Skip to content
Back to skills

Source 020d428e6bbe8b046c904098

ASecurity

End-to-end dataset curation for FiftyOne: inspect schema and quality, audit annotations, analyze class distributions, explore embeddings, find duplicates, create curated subsets, and build train/val/test splits. Works with any computer vision dataset type.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentspythonbashexpressperformancedocumentation

Works with

  • cli
  • mcp

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 7 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add gutsofpratik/OpenClaude-Skills --skill source-020d428e6bbe8b046c904098 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Source 020d428e6bbe8b046c904098?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Source 020d428e6bbe8b046c904098
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gutsofpratik-source-020d428e6bbe8b046c904098/badge)](https://www.skillsdirectory.com/skills/gutsofpratik-source-020d428e6bbe8b046c904098)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fiftyone-dataset-curation
description: "End-to-end dataset curation for FiftyOne: inspect schema and quality, audit annotations, analyze class distributions, explore embeddings, find duplicates, create curated subsets, and build train/val/test splits. Works with any computer vision dataset type."
---

# FiftyOne Dataset Curation

End-to-end curation pipeline for any FiftyOne dataset: images, video, point clouds, grouped multimodal. Run all phases sequentially or jump directly to any single phase.

## Key Directives

**ALWAYS follow these rules — no exceptions:**

### 1. Check for a loaded dataset first
```
list_datasets()
```
If none exists, offer to delegate to `fiftyone-dataset-import` skill.

### 2. Set context before any operation
```
set_context(dataset_name="<name>")
```

### 3. Launch App before any brain operator
```
launch_app()
```
Wait 5–10 seconds for initialization before executing delegated operators.

### 4. Discover schema before referencing any field
```
dataset_summary()
get_field_schema(flat=True)
```
Never hardcode field names. Adapt all field references from schema discovery results.

### 5. Discover operators dynamically
```
list_operators(builtin_only=False)
get_operator_schema(operator_uri="<uri>")
```
Always call `list_operators()` before any `execute_operator()`. Confirm the operator exists.

### 6. Confirm before mutating
Before tagging, adding fields, or deleting — present findings to the user and ask for confirmation.

### 7. Keep App open for interactive review
Do NOT call `close_app()` automatically. Leave the App running so the user can explore results.

### 8. Check delegated service before brain operations
Before running any brain operator with `delegate=True`, verify the delegated service is running:
```bash
fiftyone delegated list
```
If no services are running:
```bash
fiftyone delegated launch &
```
Wait ~5 seconds for initialization. Without this service, delegated operators will queue but never execute.

### 9. Always discover plugin names dynamically
Never assume a plugin's exact name or operator URI from documentation. Always verify:
```
list_plugins()
```
Use the exact plugin name from the output. Then:
```
list_operators(builtin_only=False)
```
Use the exact operator URI from the output. Documentation names may differ from installed names.

### 10. Generate interactive insights after each phase
After computing any metric (uniqueness, quality, similarity, etc.):
1. Calculate counts of flagged/notable samples
2. Create named saved views for the findings
3. Load the view in the App automatically
4. Present a narrative insight to the user
5. Offer to continue to the next phase

See **Insight Generation Pattern** at the bottom of this file.

---

## Phase -1 — Curation Plan

**Before doing anything else**, present a complete curation plan to the user. This sets expectations and allows them to choose which phases to run.

```
list_datasets()
dataset_summary()
get_field_schema(flat=True)
```

Present the plan:

```
## Curation Plan for <dataset_name>

Dataset: <name> | <N> samples | <media_type>
Label fields: <list from schema>
Existing brain runs: <list or "none">

### Phases I will run:
  Phase 1 — Dataset Inspection        (schema, tags, class distribution)
  Phase 2 — Data Quality Audit        (metadata, resolution, file size, image quality via jacobmarks/image_issues plugin)
  Phase 3 — Near-Duplicate Detection  (via fiftyone-find-duplicates skill)
  Phase 4 — Class Distribution        (imbalance, coverage gaps)
  Phase 5 — Embedding Exploration     (CLIP UMAP, gap detection)
  Phase 6 — Annotation Audit          (mistakenness — requires predictions field)
  Phase 7 — Curated Subset & Splits   (clean view, train/val/test)
  Phase 8 — Data Q&A                  (on-demand questions)

### Phases I will SKIP (and why):
  Phase 6 — No predictions field found. Run fiftyone-dataset-inference first.

### What you'll get:
  - Named saved views for each key finding (loadable in App)
  - Interactive insights at each step
  - A clean_<dataset_name> view with quality filters applied

Ready to start? (yes / skip to phase X / select specific phases)
```

Wait for user confirmation before proceeding. If they want to skip phases or run only specific ones, adapt accordingly.

---

## Phase 0 — Dataset Loading (Optional)

Run this phase only if no dataset is loaded yet.

```
list_datasets()
```

- If a dataset exists → `set_context(dataset_name="<name>")` and proceed to Phase 1.
- If no dataset exists → delegate to `fiftyone-dataset-import` skill.

After loading:
```
dataset_summary()
```

Confirm with user:
- Media type (image, video, point-cloud, group)
- Sample count
- Whether `persistent=True` is set

---

## Phase 1 — Dataset Inspection

**MCP tools:** `dataset_summary`, `get_field_schema`, `count_values`, `distinct`

### Steps

```
dataset_summary()
get_field_schema(flat=True)
count_values("tags")
```

From the schema, identify label fields (Detections, Classifications, Keypoints, Segmentation, Polylines, etc.) and run:
```
distinct("<label_field>.label")
```
Adapt the field path from the schema — never assume a field name.

### Report to user

Present a summary with:
- Media type and sample count
- All field names and their types
- Existing tags and counts
- Unique class names per label field
- Any existing brain runs (embeddings, similarity indexes, mistakenness scores)
- Missing or empty fields (flag these)
- **Label type awareness**: Note whether label fields are Classifications vs. Detections — this affects which export formats are valid later

### Export format note

After identifying label types, inform the user:
```
Label type: Classification → supports Image Classification Directory Tree export
Label type: Detection → supports COCO, YOLO, VOC export
For COCO export with Classification labels → must run inference first (fiftyone-dataset-inference) to generate Detection labels
```

### Grouped dataset detection

If `dataset_summary()` reports `media_type == "group"`:
1. List available group slices from the schema
2. Recommend operating on individual sensor slices for all curation phases:
   ```python
   # Python SDK equivalent (use for user guidance)
   rgb_view = dataset.select_group_slices(["rgb_center"])
   dataset.save_view("rgb_view", rgb_view)
   ```
3. Apply all subsequent brain operations to the slice view, not the full grouped dataset
4. Use sensor-prefixed brain keys (e.g., `rgb_clip_umap`, `ir_clip_umap`)

### Existing brain runs

If brain runs are reported in `dataset_summary()`:
- List them and their types (similarity, visualization, mistakenness, uniqueness, etc.)
- Offer to reuse existing runs instead of recomputing
- Ask user which phases to run and whether to skip phases with existing results

---

## Phase 2 — Data Quality Audit

See `QUALITY-CHECKS.md` for the full deep-dive workflow.

**MCP tools:** `execute_operator`, `bounds`, `histogram_values`, `mean`, `std`, `count_values`, `set_view`, `list_plugins`, `download_plugin`, `enable_plugin`

### Minimum steps

**Step 1: Compute metadata**
```
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/utils/compute_metadata")
execute_operator(operator_uri="@voxel51/utils/compute_metadata", params={})
```

**Step 2: Check for corruption (null metadata)**
```
set_view(filters={"metadata": null})
```
If samples appear → report count; offer to tag them as `"corrupted"` after confirmation.

**Step 3: Resolution analysis**
```
bounds("metadata.width")
bounds("metadata.height")
histogram_values("metadata.width", bins=20)
histogram_values("metadata.height", bins=20)
```

**Step 4: File size analysis**
```
bounds("metadata.size_bytes")
mean("metadata.size_bytes")
std("metadata.size_bytes")
```

**Step 5: Aspect ratio**
Report wide/tall outliers using ViewField pattern (see QUALITY-CHECKS.md).

**Step 6: Image quality via jacobmarks plugin (REQUIRED for image datasets)**

This is the primary method for detecting blur, brightness issues, contrast, saturation, and aspect ratio anomalies. Do NOT use custom cv2 code. Follow the discovery workflow:

```
list_plugins()
```

Find the entry matching "image_issues" or "image-quality". Note the exact plugin name from the output — **do not assume it matches the documentation name**.

If not installed:
```
download_plugin(plugin_name="jacobmarks/image-quality-issues")
```
Then run `list_plugins()` again to confirm the installed name.

Enable if disabled:
```
enable_plugin(plugin_name="<exact_name_from_list_plugins>")
```

Discover operator URIs:
```
list_operators(builtin_only=False)
```

Find operators matching `find_issues`, `compute_blurriness`, `compute_brightness`, etc. Use exact URIs.

Run each issue check (delegated, one at a time). Use the exact operator URI from `list_operators()`:
```
execute_operator(
    operator_uri="<uri_from_list_operators>",   # e.g. @jacobmarks/image_issues/find_issues
    params={"issue_mode": "SINGLE", "issue": "blurry"},
    delegate=True
)
```

Wait for completion via `list_delegated_operations()` before running the next issue check. Repeat for: `low_contrast`, `low_saturation`, `weird_aspect_ratio`, `dark`, `bright` (see note on `bright` in QUALITY-CHECKS.md).

After all checks complete:
```
get_field_schema(flat=True)
```
Identify the boolean flag fields added (e.g., `blurry`, `low_contrast`, `low_saturation`, `weird_aspect_ratio`).

Generate interactive insights — see **Insight Generation Pattern** below.

---

## Phase 3 — Near-Duplicate Detection

**Delegate to `fiftyone-find-duplicates` skill.**

Tell the user:
> "For near-duplicate detection, I'll use the `fiftyone-find-duplicates` skill."

Recommended settings to suggest:
- Model: `mobilenet-v2-imagenet-torch` (fast) or `clip-vit-base32-torch` (semantic)
- Brain key: `img_sim`
- Default threshold: 0.3

After duplicates are identified and optionally removed, return here for Phase 4.

---

## Phase 4 — Class Distribution & Imbalance Analysis

**MCP tools:** `count_values`, `histogram_values`, `distinct`, `bounds`, `mean`, `std`

For each label field discovered in Phase 1:

### Classification datasets
```
count_values("<label_field>.label")
```

### Detection datasets
```
count_values("<detections_field>.detections.label")
histogram_values("<detections_field>.detections.confidence", bins=20)
bounds("<detections_field>.detections.bounding_box")
mean("<detections_field>.detections.confidence")
```

### Analysis to surface

- **Class imbalance**: Classes with < 5% of total samples are underrepresented — flag them
- **Confidence spread**: Bimodal distribution suggests uncertain predictions
- **BBox size range**: Very small bboxes (< 1% of image area) may be low-quality annotations
- **Metadata distributions**: If metadata fields exist (time of day, weather, sensor type, etc.), run `count_values` on them to surface coverage gaps:
  ```
  count_values("<metadata_field>")
  ```
  Adapt field paths from schema — do not assume any specific metadata fields exist.

### Coverage gaps

Report under-represented attribute combinations. Example guidance:
- Few night-scene samples → recommend semantic search augmentation (Phase 5)
- Rare class < 20 samples → flag for annotation priority

---

## Phase 5 — Embedding Exploration & Gap Detection

**Delegate to `fiftyone-embeddings-visualization` skill.**

Tell the user:
> "For embedding visualization and gap detection, I'll use the `fiftyone-embeddings-visualization` skill."

### Critical parameter: num_dims

When calling `compute_visualization`, always include `num_dims`:
```
execute_operator(
    operator_uri="@voxel51/brain/compute_visualization",
    params={
        "brain_key": "<key>",
        "model": "clip-vit-base32-torch",
        "method": "umap",
        "num_dims": 2        # REQUIRED — operator fails without this
    },
    delegate=True
)
```

### UMAP dependency

`umap-learn` must be installed in the same Python environment as FiftyOne. If it fails:
```bash
# Find which python FiftyOne uses
python3 -c "import fiftyone; print(fiftyone.__file__)"

# Install to the correct environment
/path/to/correct/python -m pip install umap-learn
```

### Recommended models

- **CLIP** (`clip-vit-base32-torch`): Semantic similarity, supports text search → best for gap detection
- **DINOv2** (`dinov2-vits14-torch`): Visual similarity → best for cluster exploration

### Patch-level embeddings (detection datasets)

For detection datasets, suggest patch-level embeddings after schema confirms a detections field:
```
get_operator_schema(operator_uri="@voxel51/brain/compute_visualization")
execute_operator(
    operator_uri="@voxel51/brain/compute_visualization",
    params={
        "patches_field": "<detections_field>",   # from schema discovery
        "model": "clip-vit-base32-torch",
        "method": "umap",
        "num_dims": 2,
        "brain_key": "<detections_field>_clip_umap"
    },
    delegate=True
)
```

### Uniqueness computation

After UMAP/similarity completes, compute uniqueness to find redundant samples:
```
execute_operator(
    operator_uri="@voxel51/brain/compute_uniqueness",
    params={},
    delegate=True
)
```

After completion, generate interactive insights:
```
bounds("uniqueness")
histogram_values("uniqueness", bins=20)
```

Build named views:
```python
# Python SDK guidance — present these commands to user
# Most redundant: bottom uniqueness scores
dataset.save_view("redundant_samples", dataset.sort_by("uniqueness").limit(N))
# Most diverse: top uniqueness scores
dataset.save_view("most_unique", dataset.sort_by("uniqueness", reverse=True).limit(M))
```

Then load the insight view:
```
execute_operator("@voxel51/operators/load_saved_view", params={"name": "most_unique"})
```

### Semantic text search for subset discovery

After CLIP similarity index exists:
```
execute_operator(
    operator_uri="@voxel51/brain/sort_by_similarity",
    params={"query": "foggy day", "k": 50, "brain_key": "<sim_key>"}
)
tag_samples(["foggy"])
```
Use text queries that match domain gaps found in Phase 4.

---

## Phase 6 — Annotation Audit

See `ANNOTATION-AUDIT.md` for the full deep-dive workflow.

**MCP tools:** `execute_operator`, `get_field_schema`, `set_view`, `tag_samples`

### Prerequisite check

Check if a predictions field exists:
```
get_field_schema(flat=True)
```

If no predictions field:
> "Annotation audit (mistakenness) requires a predictions field. Delegate to `fiftyone-dataset-inference` skill first, then return here."

> Note: If the dataset has Classification labels and you want COCO-compatible predictions, use `yolov8n-coco-torch` from the Zoo — it generates actual Detection bounding boxes.

### Steps

**Step 1: Compute mistakenness**
```
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/brain/compute_mistakenness")
execute_operator(
    operator_uri="@voxel51/brain/compute_mistakenness",
    params={
        "pred_field": "<predictions_field>",    # from schema
        "label_field": "<ground_truth_field>",  # from schema
        "mistakenness_field": "mistakenness"
    }
)
```

**Step 2: Review high-mistakenness samples**
```
set_view(sort_by="mistakenness", reverse=True, limit=50)
```
Direct user to the FiftyOne App (use get_session_info() to get the URL) for visual review.

**Step 3: Tag suspicious samples** (after user confirmation)
```
tag_samples(["annotation_review"])
```

**Step 4: Check possible missing annotations**
```
set_view(filters={"possible_missing": {"$gt": 0}})
```

Full annotation audit workflow in `ANNOTATION-AUDIT.md`.

---

## Phase 7 — Curated Subset Creation & Splits

See `SUBSET-CREATION.md` for the full deep-dive workflow.

**MCP tools:** `execute_operator`, `tag_samples`, `untag_samples`, `set_view`

### Building the clean view

After quality checks and duplicate detection, construct a clean view by combining all quality filters. Use `get_field_schema(flat=True)` to discover which boolean flag fields were added by the quality plugin — never hardcode field names.

Example (adapt field names from schema):
```python
# Python SDK guidance — adapt field names from your schema
from fiftyone import ViewField as F

# Discover which quality flag fields exist via get_field_schema()
# then build the filter expression from those fields only
quality_filters = (F("blurry") == False) & (F("low_contrast") == False)

# Add uniqueness filter if computed
if "uniqueness" in field_schema:
    quality_filters = quality_filters & (F("uniqueness") >= 0.3)  # tune threshold

clean_view = dataset.match(quality_filters)
dataset.save_view("clean_<dataset_name>", clean_view)
```

Then load in App:
```
execute_operator("@voxel51/operators/load_saved_view", params={"name": "clean_<dataset_name>"})
```

Report to user:
- Total samples before cleaning
- Samples removed per quality filter
- Final clean sample count and percentage

### Option A: Uniqueness-based diverse subset
```
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/brain/compute_uniqueness")
execute_operator(operator_uri="@voxel51/brain/compute_uniqueness", params={})
set_view(sort_by="uniqueness", reverse=True, limit=<N>)
tag_samples(["diverse"])
```

### Option B: Representativeness-based coreset
```
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/brain/compute_representativeness")
execute_operator(operator_uri="@voxel51/brain/compute_representativeness", params={})
set_view(sort_by="representativeness", reverse=True, limit=<N>)
tag_samples(["coreset"])
```

### Option C: Train/val/test splits
```
# Clear any existing split tags first
untag_samples(["train", "val", "test"])

# Python SDK guidance for user:
# import fiftyone.utils.random as four
# four.random_split(dataset, {"train": 0.70, "val": 0.15, "test": 0.15})
```

### Saving and cloning
```
# Python SDK guidance:
# dataset.save_view("curated_subset", view)
# view.clone(name="<dataset_name>_curated", persistent=True)
```

Full subset workflows in `SUBSET-CREATION.md`.

---

## Phase 8 — Data Q&A Interface

Answer natural language questions about the dataset using aggregation MCP tools.

**MCP tools:** `count_values`, `bounds`, `mean`, `std`, `distinct`, `histogram_values`, `dataset_summary`, `count_sample_tags`

### Question → Tool mapping

| User question | MCP tool |
|---------------|----------|
| "How many samples have no annotations?" | `set_view(exists=["<label_field>"])` then count |
| "What is the class distribution?" | `count_values("<label_field>.label")` |
| "What % of samples are night scenes?" | `count_values("<attribute_field>.label")` |
| "What is the average bbox size?" | `mean("<detections_field>.detections.bounding_box")` |
| "How many images are untagged?" | `count_sample_tags()` |
| "What is the resolution range?" | `bounds("metadata.width")` + `bounds("metadata.height")` |
| "How many duplicate samples were removed?" | `count_values("tags")` for relevant tag |
| "What brain runs have been computed?" | `dataset_summary()` |
| "How confident are the predictions?" | `histogram_values("<pred_field>.detections.confidence", bins=20)` |
| "Are there any corrupted files?" | `set_view(filters={"metadata": null})` |

Always adapt field paths from schema discovery — never assume field names exist.

---

## Insight Generation Pattern

After any significant computation, generate and share interactive insights. This pattern applies after every phase.

### Template

1. **Compute aggregations** on the results:
   ```
   bounds("<score_field>")
   histogram_values("<score_field>", bins=20)
   mean("<score_field>")
   ```

2. **Calculate counts** for notable subsets:
   ```
   # Use set_view to count samples above/below thresholds
   set_view(filters={"<flag_field>": True})
   # Note the count from the response
   ```

3. **Create named saved views** via Python SDK (present as guidance to user):
   ```python
   # Present as Python SDK guidance
   dataset.save_view("insight_<name>", <view_expression>)
   ```

4. **Load the view in App** using the load_saved_view operator:
   ```
   execute_operator("@voxel51/operators/load_saved_view", params={"name": "insight_<name>"})
   ```

5. **Present narrative insight** with counts and percentages:
   ```
   ## Insight: <Phase Name>

   Found <N> samples (<X%>) flagged as <issue>.

   Saved view "<insight_name>" is now loaded in the App.
   → Visit the FiftyOne App (use get_session_info() to get the URL) to inspect these samples.

   Would you like to:
   - Tag these samples as "<tag>"
   - Move to the next phase
   - Dig deeper into this finding
   ```

### Example: After uniqueness computation

```
## Insight: Dataset Redundancy

Uniqueness range: <min> → <max> (mean: <mean>)

Bottom 10% by uniqueness (<N> samples) = highly redundant images — visually similar to many others.
View "redundant_samples" is now loaded in the App.

Top 20% by uniqueness (<M> samples) = maximally diverse images — best candidates for a training subset.
View "most_unique" is now loaded in the App.

Next up: Image quality checks (Phase 2). Continue?
```

---

## Delegation Map

| Sub-task | Delegated skill |
|----------|----------------|
| Import dataset if none loaded | `fiftyone-dataset-import` |
| Near-duplicate detection | `fiftyone-find-duplicates` |
| Embedding visualization | `fiftyone-embeddings-visualization` |
| Predictions for mistakenness | `fiftyone-dataset-inference` |

---

## Production Patterns (Multimodal / Grouped Datasets)

### Grouped dataset curation
```python
# Select sensor slice before brain ops (Python SDK guidance)
rgb_view = dataset.select_group_slices(["rgb_center"])
dataset.save_view("rgb_view", rgb_view)

# Run brain ops on slice with sensor-prefixed keys
# fob.compute_similarity(rgb_view, model="clip-vit-base32-torch", brain_key="rgb_img_sim")
```

### Check if similarity index supports text search
```python
# Python SDK guidance
info = dataset.get_brain_info("rgb_img_sim")
supports_text = info.config.supports_prompts  # True for CLIP-based
```

### Always save after field mutations
```python
# Python SDK guidance
view.apply_model(model, "predictions")
view.save()  # critical — never skip
```

### Thumbnail generation for large datasets (> 10k samples)
```python
# Python SDK guidance — improves App performance
# foui.transform_images(view, size=(-1, 128), output_field="thumbnail_path", output_dir=...)
# dataset.app_config.media_fields = ["filepath", "thumbnail_path"]
# dataset.app_config.grid_media_field = "thumbnail_path"
# dataset.save()
```

### ViewField patterns for quality filters
```python
from fiftyone import ViewField as F

# Resolution
F("metadata.width") > min_width
F("metadata.height") > min_height

# Color channels
F("metadata.num_channels") == 3

# Aspect ratio filters
long_filter = F("metadata.width") > 2 * F("metadata.height")
tall_filter  = F("metadata.height") > 2 * F("metadata.width")
normal_aspect = (~long_filter) & (~tall_filter)

# BBox area (relative)
rel_bbox_area = F("bounding_box")[2] * F("bounding_box")[3]

# Annotation quality (after mistakenness computation)
F("mistakenness") > 0.95
F("possible_missing") > 0
F("max_iou") > 0.75
```

---

## Reference Files

- `QUALITY-CHECKS.md` — Image quality filtering: metadata, blur, brightness, resolution, aspect ratio, CLIPScore
- `ANNOTATION-AUDIT.md` — Annotation error detection: mistakenness, hardness, IoU dedup, side-by-side review
- `SUBSET-CREATION.md` — Subset and split workflows: coreset, uniqueness, random split, saved views, cloning

Files in this skill

  • ANNOTATION-AUDIT.md9 KB
  • PROVENANCE.json215 B
  • QUALITY-CHECKS.md12 KB
  • README.md1.2 KB
  • SKILL.md23.1 KB
  • SUBSET-CREATION.md12.1 KB
  • UPSTREAM-LICENSE.txt10.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.