Skip to content

Add Semantic PDF Image Extractor skill - #289

Open
Bas (basvb1992) wants to merge 3 commits into
microsoft:mainfrom
basvb1992:submit-semantic-pdf-image-extractor-clean
Open

Add Semantic PDF Image Extractor skill#289
Bas (basvb1992) wants to merge 3 commits into
microsoft:mainfrom
basvb1992:submit-semantic-pdf-image-extractor-clean

Conversation

@basvb1992

Copy link
Copy Markdown

Summary

Adds Semantic PDF Image Extractor, a domain-neutral Agent Skill for finding and extracting meaningful photos, diagrams, charts, maps, screenshots, and other visual evidence from PDFs.

The workflow renders pages before semantic analysis, preserves captions and page context, records normalized provenance coordinates, suggests exact and near-duplicate groups, and marks uncertain assets for review. It supports flattened scans and mixed PDF layouts rather than relying only on embedded image objects.

Included

  • Agent-facing SKILL.md
  • Human-facing gallery README.md
  • Extraction profiles and normalized region contract
  • JSON Schema and starter manifest
  • Optional deterministic Python helper for rendering, cropping, validation, deduplication, and packaging

Platform and dependencies

  • Tested platform: Copilot Studio
  • Python 3.10+ is optional
  • pypdfium2 and Pillow enable helper-based rendering and cropping
  • Validation, duplicate suggestions, packaging, and self-test remain dependency-free
  • Runtimes with native PDF/image capabilities can follow the same contracts without those packages

Privacy and security

  • Contains no customer documents, images, generated extraction output, tenant details, personal paths, credentials, secrets, or connection information
  • Examples and self-tests use synthetic document names and content
  • Helper performs local file operations and makes no network requests
  • PDF content is treated as untrusted data, including explicit prompt-injection resistance
  • Runtime instructions prohibit public links, unapproved uploads, hidden metadata disclosure, and unrelated personal data

Validation

  • Final submission payload manually reviewed for customer and personal data
  • Forbidden binary/output scan passed (pdf, images, ZIPs, caches, and bytecode absent)
  • Python helper self-test passed
  • npm run check:submissions passed (91 submissions)
  • npm run import:submissions passed
  • npm run build passed (390 pages)
  • Branch diff contains only submissions/semantic-pdf-image-extractor/

Licensing

I agree that this contribution is made under the repository's MIT License.

Copilot AI lite review requested due to automatic review settings August 17, 2026 12:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new submission under submissions/semantic-pdf-image-extractor/ that defines the Semantic PDF Image Extractor Agent Skill, including its agent-facing workflow, human-facing gallery README, reference contracts, and an optional deterministic Python helper for rendering/cropping/validation/packaging.

Changes:

  • Introduces the agent-facing SKILL.md with an end-to-end extraction workflow and required ZIP output structure.
  • Adds reference contracts (region proposals, extraction profiles) and a JSON Schema describing the normalized manifest format.
  • Provides an optional Python helper (pdf_image_extractor.py) implementing rendering, cropping, duplicate suggestions, manifest validation, and packaging, plus a dependency-free self-test.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
submissions/semantic-pdf-image-extractor/SKILL.md Defines the runtime workflow, output archive structure, and security/privacy constraints for the skill.
submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py Optional deterministic helper for rendering/cropping/validation/dedup/packaging and self-test.
submissions/semantic-pdf-image-extractor/references/region-proposals.md Specifies the normalized region-proposals contract consumed by the helper and workflow.
submissions/semantic-pdf-image-extractor/references/output-schema.json JSON Schema for manifest.json output structure.
submissions/semantic-pdf-image-extractor/references/extraction-profiles.md Scenario-driven profiles for inclusion/context tuning while keeping a consistent manifest.
submissions/semantic-pdf-image-extractor/README.md Human-facing gallery overview, requirements, and example requests.
submissions/semantic-pdf-image-extractor/metadata.json Submission metadata (platforms/tags/author/version) for the gallery/import pipeline.
submissions/semantic-pdf-image-extractor/assets/manifest-template.json Starter manifest template matching the schema and workflow.
Suppressed comments (1)

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:287

  • duplicateGroupId is marked as required in references/output-schema.json, but the validator treats a missing duplicateGroupId the same as an explicit null (because it uses asset.get(...)). This means validate_manifest_data() can accept manifests that do not actually conform to the published schema.
        duplicate_group_id = asset.get("duplicateGroupId")
        if duplicate_group_id is not None:
            require_id(duplicate_group_id, f"{field}.duplicateGroupId")
        asset_group_ids[asset_id] = duplicate_group_id

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py Outdated
Comment on lines +254 to +263
for range_index, page_range in enumerate(request["pageRanges"]):
field = f"request.pageRanges[{range_index}]"
require(isinstance(page_range, dict), f"{field} must be an object")
document_id = require_id(page_range.get("documentId"), f"{field}.documentId")
require(document_id in document_ids, f"{field}.documentId is unknown")
start, end = page_range.get("from"), page_range.get("to")
require(isinstance(start, int) and not isinstance(start, bool) and start >= 1,
f"{field}.from is invalid")
require(isinstance(end, int) and not isinstance(end, bool) and end >= start,
f"{field}.to is invalid")
Comment on lines +180 to +183
require(manifest.get("schemaVersion") == SCHEMA_VERSION, f"schemaVersion must be {SCHEMA_VERSION}")
generated_at = manifest.get("generatedAt")
if generated_at is not None:
require(isinstance(generated_at, str), "generatedAt must be null or an ISO timestamp")
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 17, 2026 13:19
@basvb1992

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (5)

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:183

  • validate_manifest_data allows generatedAt to be omitted entirely (because manifest.get('generatedAt') returns None), but output-schema.json requires the generatedAt property (nullable). This can let manifests pass helper validation while still failing schema validation downstream.
    require(isinstance(manifest, dict), "Manifest root must be an object")
    require(manifest.get("schemaVersion") == SCHEMA_VERSION, f"schemaVersion must be {SCHEMA_VERSION}")
    generated_at = manifest.get("generatedAt")
    if generated_at is not None:
        require(isinstance(generated_at, str), "generatedAt must be null or an ISO timestamp")

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:249

  • Page entries should include width and height keys (nullable) per output-schema.json, but the validator currently treats a missing key the same as null (page.get(dimension)), so a schema-invalid page object can still pass helper validation.
            for dimension in ("width", "height"):
                value = page.get(dimension)
                require(value is None or (isinstance(value, int) and not isinstance(value, bool) and value >= 1),
                        f"{page_field}.{dimension} must be null or a positive integer")

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:263

  • request.pageRanges validation does not ensure from/to stay within the referenced document’s declared pageCount. This can produce a manifest that passes helper validation but describes impossible page ranges.
        start, end = page_range.get("from"), page_range.get("to")
        require(isinstance(start, int) and not isinstance(start, bool) and start >= 1,
                f"{field}.from is invalid")
        require(isinstance(end, int) and not isinstance(end, bool) and end >= start,
                f"{field}.to is invalid")

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:287

  • duplicateGroupId is marked as a required (nullable) property in output-schema.json, but the validator does not require the key to exist (a missing key is treated as None). This can allow schema-invalid assets to pass helper validation.
        duplicate_group_id = asset.get("duplicateGroupId")
        if duplicate_group_id is not None:
            require_id(duplicate_group_id, f"{field}.duplicateGroupId")
        asset_group_ids[asset_id] = duplicate_group_id

submissions/semantic-pdf-image-extractor/scripts/pdf_image_extractor.py:156

  • validate_quality checks field values but does not require the JSON Schema required keys to be present (e.g., a manifest could omit width/fileBytes entirely and still pass). Since output-schema.json marks these fields as required, the validator should explicitly enforce key presence (null is fine when allowed) before validating types/values.

This issue also appears in the following locations of the same file:

  • line 179
  • line 246
  • line 259
  • line 284
def validate_quality(value: Any, field: str, image_path: Path | None = None) -> None:
    require(isinstance(value, dict), f"{field} must be an object")
    for key in ("width", "height"):
        item = value.get(key)
        require(item is None or (isinstance(item, int) and not isinstance(item, bool) and item >= 1),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants