Skip to content

feat(people-and-planet-ai): modernize wildlife image classification with Gemini 3.8 Flash, Gemini Embedding 2, and Apache Beam RunInference - #14701

Merged
iennae merged 22 commits into
mainfrom
feat/modernize-ppai-image-classification
Nov 4, 2026
Merged

iennae merged 22 commits into
mainfrom
feat/modernize-ppai-image-classification

Conversation

@iennae

@iennae iennae commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Modernizes the People & Planet AI: Wildlife Insights (people-and-planet-ai/image-classification) sample and interactive notebook by replacing
deprecated AutoML Vision training and dedicated endpoint deployment with Gemini 3.8 Flash (gemini-3.8-flash), Gemini Embedding 2
(gemini-embedding-2), Apache Beam RunInference on Dataflow, and BigQuery VECTOR_SEARCH.

DO NOT MERGE. Once reviewed, I need to modify some sections around the checkout rather than checking out from this branch. That's a small change, but I wanted to make it possible to validate the notebook without having to have reviewers modify the notebook.

I have tested this in Colab Enterprise.

Why this matters for conservation workloads

In the LILA Science Wildlife Conservation Society (WCS) Camera Traps dataset (736,952 valid annotations across 639 species), species counts are
heavily skewed—from 95,788 photos of common species (tayassu pecari) down to 54 photos of critically endangered Sumatran rhinoceros
(dicerorhinus sumatrensis) and 1 photo of southern three-banded armadillo (tolypeutes matacus).

  • No rare species left behind: Legacy AutoML Vision required min_images_per_class = 50 to train a supervised classifier, excluding 359 of
    639 species (56%)
    —including the rarest and most endangered animals. Zero-shot classification with gemini-3.8-flash supports
    min_images_per_class = 1 while keeping Sample.FixedSizePerKey(max_images_per_class) in Apache Beam so common species do not dominate batch
    inference budgets.
  • In-place Cloud Storage inference (zero data duplication): Instead of downloading and copying gigabytes of camera trap JPEGs into a user bucket to
    build an AutoML CSV import file, Gemini reads images directly in place from gs://public-datasets-lila/wcs-unzipped/.
  • Richer ecological recovery signals in one pass: Using Pydantic Structured Outputs (WildlifeObservation), each inference returns
    species_scientific_name, taxon_family (for family-level rollups on blurry nighttime frames), common_name, animal_count, juvenile_present (to
    track breeding and population recovery), is_empty_or_misfire, and confidence, with optional habitat_region geo-fencing.
  • Rare-species visual similarity search: Uses gemini-embedding-2 (768-dimensional multimodal embeddings) and BigQuery VECTOR_SEARCH to find
    visually similar camera trap frames from a single reference sighting.
  • Zero idle endpoint cost: Replaces multi-hour training jobs and always-on model endpoints with serverless calls from Apache Beam RunInference.

Key Changes

1. Standalone DevSite Code Snippets

  • create_images_metadata_table.py ([START dataflow_wildlife_ingest_metadata]): Apache Beam pipeline that streams and cleans the LILA WCS Camera
    Traps metadata archive (gs://public-datasets-lila/wcs/wcs_camera_traps.json.zip) into BigQuery.
  • classify_wildlife_image.py ([START genai_wildlife_classify_image]): Classifies camera trap imagery in-place from Cloud Storage using
    gemini-3.8-flash on the global endpoint with structured output (WildlifeObservation) and optional habitat_region geo-fencing.
  • embed_wildlife_image.py ([START genai_wildlife_embed_image]): Generates 768-dimensional multimodal vector embeddings for camera trap imagery
    using gemini-embedding-2 on the global endpoint.
  • run_wildlife_inference.py ([START dataflow_wildlife_run_inference]): Runs class-balanced per-species sampling (Sample.FixedSizePerKey) and
    batch Gemini inference using Apache Beam RunInference (GeminiWildlifeModelHandler), writing structured ecological observations to BigQuery.
  • Removed deprecated AutoML scripts: Deleted train_model.py, deploy_model.py, and predict.py.

2. Notebook & Documentation (README.ipynb, README.md)

  • Aligned README.ipynb with the standard Google Cloud notebook layout (4-badge launch bar for Colab, Colab Enterprise, Workbench, and GitHub; cost
    calculator disclosure; guarded Colab authentication; example-project-id @param defaults with os.environ fallbacks; and IS_TESTING cleanup
    guard).
  • Replaced full-monorepo git clone and kernel restart steps with an isolated git sparse-checkout and in-place %pip install.
  • Disabled Soft Delete (--soft-delete-duration=0s) on the temporary Dataflow staging bucket to prevent retention warnings and unnecessary storage
    charges.
  • Added a runnable BigQuery VECTOR_SEARCH section over gemini-embedding-2 camera trap embeddings.

Post-Approval Actions

  • Please merge this PR for me once it is approved

iennae added 16 commits October 28, 2026 16:58
…ith Gemini and Apache Beam RunInference

Problem:
The legacy people-and-planet-ai/image-classification sample relied on deprecated AutoML Vision APIs (train_model.py, deploy_model.py, predict.py), pointed to a defunct Azure Blob Storage URL (HTTP 403) for the LILA WCS Camera Traps dataset, required copying gigabytes of camera trap images into a user bucket, excluded rare species with fewer than 50 training images, and required hours of model training plus an always-on endpoint.

Solution:
- Update create_images_metadata_table.py (region tag dataflow_wildlife_ingest_metadata) to ingest and clean WCS camera trap annotations from LILA Science's public Cloud Storage archive (https://storage.googleapis.com/public-datasets-lila/wcs/wcs_camera_traps.json.zip) into BigQuery.
- Add classify_wildlife_image.py (region tag genai_wildlife_classify_image) using Gemini 3.8 Flash (gemini-3.8-flash) on the global endpoint with Pydantic Structured Outputs (WildlifeObservation) and habitat geo-fencing (habitat_region) to classify camera trap images in-place from gs://public-datasets-lila/ into species, taxonomic family, common name, animal count, juvenile presence, and empty/misfire flags.
- Add embed_wildlife_image.py (region tag genai_wildlife_embed_image) using Gemini Embedding 2 (gemini-embedding-2) to generate 768-dimensional multimodal image embeddings for 1-shot rare species similarity search with BigQuery VECTOR_SEARCH.
- Add run_wildlife_inference.py (region tag dataflow_wildlife_run_inference) using Apache Beam Sample.FixedSizePerKey and RunInference (GeminiWildlifeModelHandler) to run class-balanced batch inference directly against gs://public-datasets-lila/wcs-unzipped/ and write structured observations to BigQuery.
- Remove deprecated train_model.py, deploy_model.py, and predict.py.
- Update e2e_test.py, requirements.txt, requirements-test.txt, README.ipynb, README.md, and people-and-planet-ai/README.md for DevSite style compliance and end-to-end test coverage.
…ook setup/cleanup cells, and cite verified WCS counts

Problem:
README.ipynb replaced the original Apache Software Foundation (ASF) license cell header, lacked clarity on execution vs. setup time, required navigating to external console pages to enable APIs and create/clean up resources, and used approximate species skew wording.

Solution:
- Restore the original ASF Apache 2.0 header in Cell 0 of README.ipynb while keeping 2026 copyright headers on the new/refactored Python files.
- Clarify the time estimate (~25-30 minutes total: ~10 minutes Dataflow execution plus ~15-20 minutes setup and exploration) and remove the 'free if you use Cloud credits' parenthetical.
- Add executable cells in README.ipynb to enable the required APIs, create the regional Cloud Storage bucket and BigQuery dataset, and clean up the BigQuery dataset and Cloud Storage staging files directly in the notebook.
- Update the species distribution section with verified counts from wcs_camera_traps.json.zip (736,952 valid annotations across 639 species; 95,788 tayassu pecari images vs. 54 dicerorhinus sumatrensis and 1 tolypeutes matacus; 359 of 639 species having fewer than 50 images).
… notebook_template.ipynb

Problem:
README.ipynb used custom legacy headings and project configuration variables instead of the standard GoogleCloudPlatform/vertex-ai-samples notebook_template.ipynb layout.

Solution:
- Adopt the vertex-ai-samples notebook_template.ipynb header and section hierarchy (# Title + 4-way launch table, ## Overview, ## Get started, ### Install required packages, ### Import libraries, ### Authenticate your notebook environment (Colab only), ### Set Google Cloud project information, ### Initialize Gen AI SDK, tutorial sections, and ## Cleaning up).
- Standardize notebook configuration parameters on PROJECT_ID, LOCATION, BUCKET_NAME, CLOUD_STORAGE_DIRECTORY, BIGQUERY_DATASET, BIGQUERY_TABLE, and PREDICTIONS_TABLE across all pipeline, interactive inference, embedding, and cleanup cells.
…ll 0 of README.ipynb

Problem:
Aligning README.ipynb with notebook_template.ipynb replaced the original Apache Software Foundation (ASF) license notice in Cell 0.

Solution:
Restore the original Licensed to the Apache Software Foundation (ASF) notice in Cell 0 of README.ipynb.
…CT_ID and add Colab Enterprise VPC note

Problem:
Cell 13 rendered @PARAM form fields asking the user to paste in a bucket name and dataset name, and users opening Colab Enterprise in organizations with constraints/compute.skipDefaultNetworkCreation encountered a blank Network dropdown and read-only preview mode.

Solution:
- Keep only PROJECT_ID and LOCATION in the Set Google Cloud project information @PARAM cell.
- Add a dedicated Create a Cloud Storage bucket and BigQuery dataset subsection that automatically derives BUCKET_NAME = f'{PROJECT_ID}-wildlife-insights' and BIGQUERY_DATASET = 'samples', enables the required APIs, and creates the bucket and dataset inline.
- Add a collapsible note under the launch buttons explaining how to create the default VPC network and regional subnet if the Network dropdown is blank when importing into Colab Enterprise.
@iennae
iennae requested review from a team as code owners October 29, 2026 22:26
@product-auto-label product-auto-label Bot added samples Issues that are directly related to samples. api: people-and-planet-ai labels Oct 29, 2026
@snippet-bot

snippet-bot Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Here is the summary of changes.

You are about to add 4 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request modernizes the wildlife image classification sample by transitioning from legacy AutoML Vision to Gemini-based solutions. It introduces scripts for multimodal classification using gemini-3.8-flash with optional habitat geo-fencing, generating vector embeddings with gemini-embedding-2 for similarity search, and running class-balanced batch inference using Apache Beam on Dataflow. The Jupyter notebook and documentation are updated to reflect this serverless, lower-cost architecture. The reviewer feedback focuses on enhancing pipeline robustness and error handling, including implementing retries for external HTTP downloads, wrapping individual element processing in try-except blocks to prevent pipeline crashes, catching general exceptions instead of specific GCP client exceptions during Beam pipeline execution, and safely handling the embedding response attributes.

@amcolin amcolin 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.

This is a fantastic modernization of the camera trap sample! Moving away from the legacy custom-trained models to zero-shot Foundation Models using gemini-3.8-flash, google.genai, and Pydantic structured outputs is a huge improvement and makes the code so much cleaner.

I tested this locally and everything works great. I have a few optional suggestions ("nits") below to make the local developer experience a bit smoother, but feel free to merge this whenever you're ready!

… references to main

Problem:
- Vertex AI Workbench (rebranded to Agent Platform Workbench) deprecated the standalone /deploy-notebook flow; the download_url link now only shows an informational banner on the VM instances page requiring a pre-existing running instance.
- The notebook launch badges and sparse-checkout BRANCH constant pointed to feat/modernize-ppai-image-classification during testing.

Solution:
- Remove the visible Open in Workbench badge from people-and-planet-ai/image-classification/README.ipynb while keeping the Colab, Colab Enterprise, and GitHub badges.
- Switch all branch references in people-and-planet-ai/image-classification/README.ipynb and README.md back to main so PR #14701 is ready for merge.
Problem:
- The Colab Enterprise badge logo URL on lh3.googleusercontent.com fails to load when running Jupyter on localhost.
- The notebook setup cell executed apt-get and git sparse-checkout commands unconditionally, failing or resetting working trees when run locally outside Google Colab.
- e2e_test.py raised a KeyError during pytest collection if GOOGLE_CLOUD_PROJECT was not set.
- Image MIME types were hardcoded to image/jpeg when constructing Part.from_uri, which would fail if users adapted the scripts to PNG or WebP images.
- The Dataflow batch inference runtime estimate in README.ipynb did not reflect full 639-species runs (~1.5-3.5 hours).

Solution:
- Update the Colab Enterprise logo URL to https://www.gstatic.com/pantheon/images/bigquery/welcome_page/colab-enterprise-logo.png.
- Guard the ephemeral Colab apt-get and git sparse-checkout setup behind if "google.colab" in sys.modules.
- Use os.environ.get("GOOGLE_CLOUD_PROJECT") with pytest.skip(..., allow_module_level=True) in e2e_test.py.
- Use mimetypes.guess_type(image_gcs_uri) with an image/jpeg fallback in run_wildlife_inference.py, classify_wildlife_image.py, and embed_wildlife_image.py, and add beam.Reshuffle() before RunInference.
- Document the ~1.5-3.5 hour full-dataset Dataflow runtime in README.ipynb while noting that interactive sections below can run immediately.
…lake8 I100

Problem:
- The repository-level flake8 lint session treats sibling script imports in people-and-planet-ai/image-classification/e2e_test.py as third-party modules because --application-import-names only lists top-level directories, causing I100 ('import classify_wildlife_image' should be before 'import pytest').

Solution:
- Order the imports alphabetically in people-and-planet-ai/image-classification/e2e_test.py so flake8-import-order passes.
@iennae
iennae merged commit 7205a97 into main Nov 4, 2026
14 of 22 checks passed
@iennae
iennae deleted the feat/modernize-ppai-image-classification branch November 4, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: people-and-planet-ai samples Issues that are directly related to samples.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants