How Wherobots builds with NVIDIA to let AI see the physical world Posted on September 30, 2026October 4, 2026 by Damian AI was blind to the physical world, but Wherobots is giving AI the ability to understand it using data, compute, and NVIDIA GPUs The world and what happens in it is digitized by petabytes of raw and derivative spatial datasets of various data types and scales, and the potential for applying AI to it is immense. But the AI models and agents we use every day need connectivity to tools that turn this data into usable insights and relationships. Wherobots gives AI the ability operate on and understand raw physical-world data, and NVIDIA RTX PRO 6000 Blackwell Server Edition GPUs are core to it. Architecturally here’s how this works at a high level. Wherobots: Integrates with your open data architecture: by utilizing data lakes like Amazon S3 and Iceberg catalogs such as Databricks Unity Catalog and AWS Glue Data Catalog as the center of truth, you can integrate Wherobots into your existing data architecture. Brings the spatial capability: data teams and their agents have the capability to solve the most challenging physical world problems by directing Wherobots to extract insights from multi-dimensional, raster, vector, and tabular datasets of various formats and scales. Is plug-and-play: in minutes, you can enable AI coding tools like VS Code, Claude Code, and Cursor to direct Wherobots operations on this data and build innovations with AI. Under the hood, we deploy RTX PRO 6000 Blackwell GPUs and libraries on tasks they are optimized for. RTX PRO 6000 Blackwell GPUs run at the core of RasterFlow, which is the preprocessing and inference solution designed to make insight extraction from satellite and aerial sensor datasets easy. We also leverage NVIDIA CUDA™ streams to optimize performance. RasterFlow crunches pixels with a distributed fleet of NVIDIA G7e Amazon Web Services (AWS) instances, accelerated by RTX PRO 6000 Blackwell GPUs. These GPUs, built on the groundbreaking Blackwell architecture, feature the second-generation Transformer Engine to deliver unprecedented performance, efficiency, and scale for generative AI and accelerated computing. SedonaDB, which WherobotsDB utilizes in a distributed mode, can run spatial joins directly on the ray tracing cores inside RTX PRO 6000 Blackwell GPUs to deliver the best price-performance for a class of spatial join types. Let’s dig in. RasterFlow on NVIDIA RasterFlow prepares large scale imagery data for inference and runs computer vision models on a distributed inference architectureIts goal is to give customers the ability to generate insights from this data across physical areas of interest of any scale, enabling real-world applications such as: Last-Mile Delivery Routing: Identifies sidewalks (using models like Tile2Net) which can help determine viable navigational routes for both manual and autonomous delivery systems. Property Risk Analytics: Can aid in quantifying the number of properties facing flood risk across coastal cities by embedding FEMA flood zone designations into parcel-level data, helping insurers, reinsurers, and real estate developers evaluate policy and property portfolios. Since its introduction in private preview, we continue to optimize the RasterFlow architecture, reaching a point where mosaicking and inference tasks cost 2–3x less than the next best option at scale. For instance, RasterFlow supports Meta’s Segment Anything Model (SAM3) for general-purpose object detection on Earth Observation imagery. With the efficiencies we’ve achieved, you can use SAM3 to detect objects from high-resolution 30cm imagery for less than $25 per 1,000 square kilometers. Example of roof detections on 30cm imagery using Meta’s Segment Anything Model (SAM3) RasterFlow uses CUDA™ streams to keep GPUs busy Data movement can kill inference performance, so we use NVIDIA CUDA™ streams and RTX PRO 6000 Blackwell GPUs to address this bottleneck. Every batch of satellite imagery patches travels from host memory to the GPU before the model can run, and every batch of prediction patches travels back before RasterFlow can merge it into a seamless output mosaic. If we ran these steps serially one after another, our GPUs would idle on PCIe transfers with every forward pass. CUDA™ streams remove that idle time. A stream is an ordered queue of GPU work, where different kinds of work in different streams can execute concurrently. . RTX PRO 6000 Blackwell GPUs include dedicated copy engines that move data across PCIe independently of the compute cores, which means a transfer in one stream proceeds while a model forward pass runs in another. RasterFlow drives this through PyTorch’s torch.cuda.Stream API. This inference loop has separate upload and download streams feeding two reusable buffer slots. While the model runs batch N on the first stream, a second stream copies the next batch to the GPU, and a download stream drains the predictions from batch N-1 back to host memory, where the CPU merges patches in the mosaic accumulator. At steady state three batches are in flight at once: one uploading, one computing, one landing in the output. RasterFlow allocates these buffers once and reuses them for every batch. CUDA™ events sequence the handoff between streams, so data movement never starts before the forward pass that produced it finishes. This allows the GPU to stay busy from the first patch to the last. What’s next: GPU-native decompression with NVIDIA nvCOMP™ and Zarr RasterFlow stores every analysis-ready mosaic as a Zarr store of compressed chunks on Amazon S3, and today those chunks decode on the CPU. nvCOMP™, NVIDIA’s library of GPU-accelerated compression codecs, can move that decode step onto the GPU itself, and we are looking forward to nvCOMP™-backed codec support landing in Zarr. The payoff of this feature is three-fold. First, compressed chunks cross PCIe instead of raw pixels, so each transfer carries fewer bytes. Second, decompression runs at GPU memory bandwidth (which is higher than CPU) and the decoded array materializes directly in GPU memory where the model needs it. Finally, CPU cores are free for the work only they can do: assembling patches and merging predictions into the mosaic. RasterFlow use cases RasterFlow continues to unblock customers who need to extract insights from global-scale imagery datasets. We are actively working with customers across multiple industries, and utilizing RasterFlow to deliver results like these: The Fields of the World global run produced globally consistent agricultural field-boundary data for land-use monitoring, food-system analysis, and model development. We executed this run using RasterFlow, and led it in partnership with Taylor Geospatial, Microsoft AI for Good Lab, and Nasa Harvest. With RasterFlow you can run this same model with 10m Sentinel-2 imagery, including cloud-free mosaic generation and model inference, for less than $1 per 10,000 square kilometers. Agricultural field boundaries predicted by Taylor Geospatial’s Fields of the World model running on RasterFlow. We are actively supporting the US Forest Service in their research, and our joint goal is to bring their new FireCon model online in the second half of this calendar year. This initiative is key to delivering far more accurate daily wildfire containment suitability mapping system for active wildfires of the Western US, enabling better planning, management, and response to wildfires. FireCon model predictions for the best fire control points generated across the western United States by RasterFlow. SedonaDB on NVIDIA WherobotsDB runs a distributed version of SedonaDB, which customers use to efficiently process and join two dimensional raster and vector data. In SedonaDB 0.4, we shipped a GPU-accelerated spatial join as an extension that runs on RTX PRO 6000 Blackwell GPU ray tracing cores. In practice, almost all spatial analyses are powered by one or more spatial joins, which are computationally intensive and frequently a bottleneck in spatial queries. But SedonaDB with GPU acceleration provides relief, and delivers up to a 5.93x speedup and a 59.02% cost reduction on our standard spatial benchmark. The research behind it, RayBooster: A Ray Tracing Engine to Accelerate SedonaDB, was accepted to VLDB 2026 in the Industry Track, and we developed it with The Ohio State University. Attempts to accelerate traditional analytical database workflows using GPUs have typically required utmost care and/or rethinking of a workflow to minimize data transfer between the existing workflow (on the CPU) and the accelerated implementation (on the GPU). The spatial join is an ideal candidate for this type of acceleration because it is computationally intensive, and relatively small amounts of data transfer can result in large numbers of computations. Furthermore, the required data transfer is only a single direction; geometries must be transferred to the GPU but need not be transferred back. Another limitation to GPU acceleration of traditional database workflows has been a requirement for a potentially large amount of GPU memory to avoid completely rewriting a workflow with careful attention to the sizes of the inputs. SedonaDB’s join implementation was built to partition large joins into smaller ones that can be run with limited memory with its CPU-based join, and while this heuristic needs to be tuned differently for GPUs, we did not need to rewrite our GPU implementation to support arbitrarily large joins. This extends the acceleration we observed to a wider range of GPUs that customers may have available. Deploying AI on physical world data with NVIDIA High complexity, scale, and costs have limited the utilization of spatial data, resulting in a significant gap between AI and physical world data. This gap is closing fast and as it closes, humans are more capable of directing AI towards solutions that drive top, bottom, and the sustainability lines of their business. Through our investments in Apache Sedona and Wherobots, and by building on NVIDIA technology, data teams and their agents are becoming increasingly capable of innovating and operating solutions with physical world data, under budget, with the talent and AI tools they already have, regardless of spatial data type and degree of complexity. Key takeawaysWherobots gives AI the ability to operate on and understand raw physical-world data, and NVIDIA GPUs are core to it. Wherobots deploys NVIDIA GPUs and libraries on tasks they are optimized for.RasterFlow crunches pixels with a distributed fleet of NVIDIA G7e AWS instances, accelerated by RTX PRO 6000 Blackwell GPUs. CUDA streams keep three batches in flight at once, driving mosaicking and inference costs to typically 2-3x less than the next best option at scale. SAM3 on 30cm imagery runs for under $25 per 1,000 square kilometers.SedonaDB runs spatial joins directly on the ray tracing cores inside NVIDIA GPUs. In SedonaDB 0.4, Wherobots shipped a GPU-accelerated spatial join that delivers up to a 5.9x speedup and a 59% cost reduction on the standard spatial benchmark. The research paper, RayBooster: A Ray Tracing Engine to Accelerate SedonaDB, was accepted to VLDB 2026 in the Industry Track.RasterFlow is validated in production with Taylor Geospatial, Microsoft AI for Good Lab, and NASA Harvest (Fields of the World), and is actively supporting the US Forest Service (FireCon) to deliver daily wildfire containment suitability mapping across the Western US in H2 2026. Get Started with Wherobots Try Now
RasterFlow is now available in Public Preview Posted on September 15, 2026October 4, 2026 by Ryan Avery RasterFlow makes planetary-scale earth intelligence workflows easy and costs predictable. We are excited to announce that RasterFlow is now in Public Preview, opening up the power of planetary scale Earth Intelligence to all Wherobots Professional Edition customers! RasterFlow let’s you solve complex monitoring challenges with vision-language models or tailored models for specific use cases, without needing to manage complex raster preparation and inference infrastructure. Teams are already running RasterFlow at planetary scale: The USDA Forest Service uses RasterFlow to deliver wildfire containment predictions to its Wildfire Risk Management Division at an operational cadence. Miraterra uses RasterFlow to predict agricultural field boundaries across the U.S. Midwest and Canada’s Prairie Provinces, then joins detailed microbial samples and geospatial embeddings to those boundaries. Taylor Geospatial worked with Wherobots to produce 8.2 billion global field boundaries for the Fields of the World program. Join our Public Preview virtual event, Pixels to Predictions: Planetary-Scale Earth Observation with RasterFlow, for a live walkthrough of built-in models, predictable pricing, and real customer pipelines. Register Here → Join Public Preview Event: Pixels to Predictions with RasterFlow. Register HERE RasterFlow is a serverless image preparation and computer vision engine that makes it easy to extract insights from large scale raster datasets. It builds mosaics from multiple raster data sources, runs inference with computer vision models, and vectorizes the results, through a high-level API that simplifies the complexity of raster pipelines and distributed computing. You pay RasterFlow usage using a predictable, low cost pricing model that scales with your area of interest. With RasterFlow’s built-in models, users can instantly launch tasks for common Earth Intelligence use cases. For instance, RasterFlow includes Meta’s SAM3 model for text-prompted object detection, Taylor Geospatial’s Fields of the World model for agricultural field boundaries, Meta’s CHM v1 for estimating tree canopy height. You can also bring your own, and we are expanding the set of open models we offer out-of-the box (let us know which open models you want offered and we can add them). Market leading pricing that you can predict RasterFlow’s predictable pricing allows you to estimate the cost of your tasks before they run. The price for each task is based on the data volume processed, so you can accurately estimate the cost of any task before execution. And pricing scales linearly with the size of your area of interest, so you can extrapolate the costs from smaller test runs to planetary scale. Data volume = Area in km² × Pixels per km² × Bands × Time periods The four inputs are: Area: Size of your AOI in km² Pixels per km²: Determined by input resolution; finer imagery means more pixels Bands: For example, 4 for RGB + NIR Time periods: For example, 3 annual observations Let’s walk through a concrete example. If you wanted to find every solar panel array across a 500 km² county, you could use the built-in SAM3 model and a simple text prompt (“solar panel”) to generate detections. Here’s the code to launch this task across your area of interest using RasterFlow’s built-in support for 30cm imagery from USDA’s National Agriculture Imagery Program (NAIP): Pythonfrom rasterflow_remote import RasterflowClient from rasterflow_remote.data_models import GeometryModelRecipes rf = RasterflowClient() detections = rf.predict_mosaic_geometries_recipe( aoi="s3://your-bucket/county.parquet", start=datetime(2022, 1, 1), end=datetime(2023, 1, 1), model_recipe=GeometryModelRecipes.SAM3_TEXT_GEOMETRY, text_prompt="solar panel", confidence_threshold=0.5, ) print(detections.uri) # GeoParquet containing detected solar-panel polygons To calculate the price for this task, we first determine the number of input pixel values based on the size of the AOI (500 km²), the resolution of the dataset (30cm NAIP), the number of bands (4), and the number of time periods (1). Price = Data volume × Task-specific rate Then, for mosaic generation and inferencing tasks, we apply a “complexity factor” to account for differences in task processing. For instance, building a mosaic from NAIP imagery is simpler than creating a cloud-free composite from Sentinel-2 imagery, so we apply a 0.1× multiplier. Similarly, different models have different complexity factors, so we apply the relevant inference complexity factor (in this case, 1.0× for SAM3). Finally, each RasterFlow task has a specific price that may vary by compute region. Combining these factors, we arrive at our total costs for these tasks: TaskComplexity factorRasterFlow Spatial Units (SU)Price per SUCostMosaic generation (NAIP)0.1×2.22$0.75$1.67Inference (SAM3)1.0×22.22$1.50$33.33Total$35.00 For more details on RasterFlow pricing, see our pricing page and documentation. To see some example solar panel detections for Marion County, Oregon, here is an interactive visualization: Check out our viewer to explore these results further. Or, for more details about our Text to Detections support with SAM3, see this blog post: Detecting Objects From Text Prompts with RasterFlow and SAM3. Your mosaics, predictions, and vectors in your storage Wherobots storage integrations connect RasterFlow directly to your own S3 buckets. Tasks can read your areas of interest and proprietary imagery from your buckets, then write mosaics, predictions, and vectorized results back to them. Wherobots securely manages the required roles and credentials, so you can focus on your workflows instead of wrangling permissions. Pythonfrom rasterflow_remote import RasterflowClient, DatasetEnum client = RasterflowClient() result = client.build_mosaics( datasets=[DatasetEnum.S2_MED_HARVEST], aoi="s3://my-company-data/aois/project.parquet", # one or multiple geometries start=datetime(2024, 1, 1), end=datetime(2025, 1, 1), bucket="s3://my-company-data/rasterflow/results", ) print(result.first_row_mosaic) "s3://my-company-data/rasterflow/results/mosaics/<run-id>/mosaic_index.parquet" Outputs remain in open, interoperable formats: Zarr for mosaics and predictions, GeoParquet for vectorized results, and Iceberg tables through managed catalogs. Your data and results remain in the storage your applications already use, without a separate migration or export workflow. Built-in visualization to inspect your Earth observation insights Visual inspection is essential for validating inference results at scale, but large raster outputs are difficult to explore in their raw form. Wherobots lets users instantly layer mosaics, model predictions, and vectorized results on an interactive map, making it easy to assess quality, tune thresholds, and spot misaligned or spurious detections. Every completed RasterFlow task includes a one-click link to view its results in the Workload History. RasterFlow also includes tasks that optimize existing Zarr stores for interactive viewing by adding image pyramids, downsampled overviews, and histogram statistics. The built-in map client then streams coarse tiles when zoomed out and full-resolution pixels when zoomed in, delivering responsive exploration at any scale. For example, see this interactive visualization of model outputs from SAM3: How RasterFlow compares to Google Earth Engine Google Earth Engine provides a deep planetary imagery catalog and a strong environment for exploratory analysis. But for planetary scale workflows, Google Earth Engine has some significant limitations: Limited cost predictability. Earth Engine bills in EECU-hours, so you only learn the total cost after the job finishes. With RasterFlow, you can calculate the cost of your tasks before you run them, eliminating uncertainty and potential billing surprises. Build your own inference pipelines. Building a planetary scale earth observation pipelines with computer vision models requires integration with Vertex AI and custom pipeline development. Workflows that integrate with imagery data sources, patch tiles, handle seams, and maintains georeferences are costly to develop and maintain. RasterFlow packages mosaicking, inference, and vectorization as built-in tasks with a simple API. Results are siloed in Earth Engine. Analysis results are stored in Earth Engine, which is separate from Google Cloud Platform or Google Cloud Storage. Using them elsewhere requires queuing export jobs, so a team building on AWS pays both export and egress costs. Mosaicking cost. Because of these export costs, mosaicking costs on Earth Engine can be significantly higher for developers in AWS. For instance, generating and exporting a Sentinel-2 mosaic with all 12 bands for 150,000 km² costs about $50 in Earth Engine vs. $13.50 for RasterFlow. Customer impact at planetary scale Fields of the World: a global field-boundary layer Fields of the World is a Taylor Geospatial effort to produce globally consistent agricultural field-boundary data for land-use monitoring, food-system analysis, and model development. Taylor Geospatial partnered with Wherobots to run their PRUE model on RasterFlow for the 2024 to 2025 global release. Read about the Fields Of The World (FTW) Project. We achieved this with three tasks that you can run today: Building mosaics to create the seasonal Sentinel-2 composites Model inference to run the PRUE model globally Vectorization to convert the per-pixel predictions into field boundaries in GeoParquet. StageArtifactSizeFeature COGs90,918 objects153 TBFeature Zarr mosaic363,999 objects, 7,499,140 logical chunks150 TBPrediction Zarr mosaic84,877 objects, 8,982,630 logical chunks45 TBVector output, GeoParquet1,000 objects, 8,217,195,679 rows675 GB In total, we generated 348.7 TB across 540,794 objects, to produce 8.2 billion field boundaries. The full pipeline write-up is in Fields of the World: a GeoAI pipeline on RasterFlow. Daily wildfire containment mapping with the USDA Forest Service FireCon model predictions for the best fire control points, generated across the western United States by RasterFlow. The USDA Forest Service FireCon model produces containment-suitability maps for active wildfires across the Western United States. Because fuel, terrain, weather, and fire conditions change continuously, yesterday’s map may not reflect the conditions crews face today. Previously, the cost and complexity of running the full pipeline limited how frequently the team could update these maps. RasterFlow changes that by ingesting potential control location data, terrain characteristics, weather forecasts, and daily soil moisture readings, normalizing the data and running the full inference pipeline end-to-end in a matter of hours rather than a full day. That dramatic reduction in both runtime and cost means the team can afford to run the pipeline multiple times per day, giving frontline response teams a current view of the fire landscape. The results are also delivered as multi-resolution raster layers, so they render smoothly whether crews are looking at a broad regional view or zooming in on a specific fire line. In practice, this translates directly into better-informed containment and resource decisions on the ground, especially during fast-changing fire conditions where yesterday’s map simply isn’t good enough. Get started today RasterFlow is available now to Wherobots Cloud Professional Edition users: Sign in to Wherobots Cloud. Build a mosaic or run a built-in model by starting with one of these notebooks: Detect objects with SAM3. Build a cloud-free Sentinel-2 mosaic. Find agricultural field boundaries. Estimate canopy height with Meta CHM v1. Register for the Public Preview webinar. Key takeawaysRasterFlow is now in Public Preview, opening up the power of planetary-scale Earth observation to all Wherobots customers, available today to Wherobots Cloud Professional Edition users.Predictable, pre-execution pricing. Pricing is based on data volume processed (Area in km² × Pixels per km² × Bands × Time periods × task-specific rate), so you can accurately estimate the cost of any task before execution and extrapolate from smaller test runs to planetary scale.An alternative to Google Earth Engine. Unlike Earth Engine’s EECU-hour billing that reveals cost only after a job finishes, RasterFlow lets you calculate costs up front and keeps results in your own storage. A 150,000 km² Sentinel-2 mosaic with all 12 bands costs about $50 in Earth Engine vs. $13.50 for RasterFlow.Proven at planetary scale with built-in models. RasterFlow ships with Meta’s SAM3, Taylor Geospatial’s Fields of the World, and Meta’s CHM v1. RasterFlow powers wildfire containment predictions for the USDA Forest Service, field-boundary work for Miraterra Soil, and the 8.2 billion global field boundaries Taylor Geospatial produced for Fields of the World. See a live walkthrough of Rasterflow. Save Your Spot
How well does SAM3 detect building footprints? We asked the Wherobots Spatial AI Coding Assistant Posted on May 28, 2026October 4, 2026 by Philip Darringer In a recent post, we showed how easy it is to use RasterFlow and Meta’s Segment Anything 3 Model (SAM3) to detect features in the physical world. A single end-to-end pipeline built a 133 GB NAIP mosaic of Marion County, Oregon, ran SAM3 against it with text prompts spanning eight classes, and produced approximately one million detection polygons in a Wherobots table including roughly 312,000 building roofs. That is an impressive result on its own. But once the inference job finished, the obvious next question was: are these detections any good? Specifically, how well do they agree with an independent reference dataset of building footprints? The Overture Maps Foundation publishes a global buildings dataset that is freely available in the Wherobots Hub. If I could compare the SAM3 roof detections against Overture for the same county, I would have a first-pass evaluation of whether SAM3 is finding the right things in roughly the right places. The catch: I am a product manager, not a data scientist, and I am not well-versed in the standard techniques used to evaluate the output of remote-sensing models. Intersection-over-union, recall and precision curves, confidence calibration, the right metric coordinate reference system… I knew the terms, but I had not performed an evaluation like this from scratch before. That is exactly where the Wherobots Spatial AI Assistant comes in. Upcoming Session: See an AI agent take plain-language question and orchestrating pipelines to end results using RasterFlow and the Wherobots MCP server. Evaluating SAM3 on Aerial Imagery in Four Prompts I opened a conversation with the assistant inside Claude, using the Wherobots MCP server. I described what I had: a fresh SAM3 detection table in org_catalog.sam3_marion_db.sam3_outputs, and a goal of comparing it to Overture buildings. The full session took four prompts. Prompt 1: Describe the data. “can you see SAM3 results in org_catalog.sam3_marion_db.sam3_outputs? can you tell me more about this dataset of SAM3 detections for Marion County OR?” Within seconds, the assistant had walked the catalog, run summary statistics, and returned a complete profile: eight detection classes (layers), around one million total rows, 312k roofs, confidence scores ranging from 0.50 to 0.96, the table’s spatial extent, and the source mosaic. One of the queries it ran was a simple breakdown of the detections by class (layer): SELECT layer, COUNT(*) AS n, AVG(bbox_score) AS avg_score FROM org_catalog.sam3_marion_db.sam3_outputs GROUP BY layer ORDER BY n DESC A sample of SAM3 roof detections (blue) over NAIP imagery in Marion County, Oregon. Each polygon is a segmentation mask, not a bounding box. Prompt 2: Design the comparison. “if I wanted to compare the buildings (roofs) detecting with SAM3 against the building footprints in the overture dataset in wherobots_open_data.overture_maps_foundation, how would I do that?” The assistant designed a four-stage approach: clip both datasets to a Marion County area of interest; spatial-join them on intersection; compute intersection-over-union per pair; and aggregate to recall, precision, and calibration metrics. It also surfaced a set of caveats: Roofs are not footprints, since overhangs and occlusion mean the shapes will never match exactly. Overture is not ground truth in rural areas and is likely to miss buildings. The 0.5 confidence cutoff was already baked into the SAM3 outputs, which limits any precision-recall analysis to the upper half of the confidence range. Area math must be performed in UTM zone 10N rather than EPSG:4326. These considerations gave me confidence that the analysis was well thought through and likely accurate. Prompt 3: Use the right area of interest. Rather than a rough bounding box, I wanted the comparison clipped to the actual Marion County admin boundary, fetched from a trustworthy source. “can you use wkls to get the official admin boundary from Overture for Marion County, like this: gdf = gpd.read_file(wkls['us']['or']['Marion County'].geojson())” The assistant rebuilt the join strategy around the admin polygon generated using wkls. It pulled the boundary as a single-row Spark view that could be broadcast into the spatial joins, and combined an Iceberg bounding-box prefilter on Overture with an exact ST_Intersects predicate against the real Marion County shape. The generated code was straightforward: import wkls import geopandas as gpd gdf = gpd.read_file(wkls['us']['or']['Marion County'].geojson()) aoi_geom = gdf.geometry.iloc[0] aoi_wkt = aoi_geom.wkt sedona.sql(f""" CREATE OR REPLACE TEMP VIEW aoi AS SELECT ST_GeomFromWKT('{aoi_wkt}') AS geom """) Prompt 4: Let’s do the analysis in a notebook. “can you create this analysis in a Jupyter notebook that I can run with Wherobots?” The assistant returned a 24-cell notebook covering everything from SedonaContext setup through the final visualization. I ran it in VS Code using a Wherobots cloud runtime. The rest of this post walks through the code and the results. Comparing SAM3 Building Detections to Overture Footprints Here’s the notebook: sam3_vs_overture_marion.ipynb The notebook has four sections: Setup and constants. Setting up imports, initializing the SedonaContext, defining the target CRS (EPSG:32610, UTM zone 10N), IoU thresholds, and table names. It’s worth noting out that the assistant chose UTM zone 10N automatically (the right metric CRS for western Oregon) without me having to ask. Fetch the AOI from wkls. The notebook calls wkls['us']['or']['Marion County'].geojson(), reads the result into a GeoPandas frame, extracts the polygon’s WKT and bounding box, and registers a one-row aoi Spark view. It then renders the boundary on a SedonaKepler map so I could visually confirm I had the right AOI before running anything expensive. Sanity counts. Two COUNT(*) queries over the SAM3 and Overture tables, both clipped to the admin boundary: SAM3 roofs inside Marion County: 261,414 (the full dataset has 312k; the remainder fell outside the official admin polygon). Overture buildings inside Marion County: 146,642. SAM3 finds notably more candidate roof shapes than Overture has buildings. Filtered AOI views. Two temporary views, sam3_roofs and overture_bldgs, each with the lon/lat geometry and a UTM-projected geometry. The Overture view uses a two-stage filter: a bounding-box prefilter that takes advantage of Iceberg column statistics and an exact ST_Intersects predicate against the admin polygon for Marion County. Spatial join. This step matches candidate buildings. For every pair of SAM3 roof and Overture building whose geometries intersect, the query computes the intersection area, the two source areas, and the IoU: SELECT s.sam3_id, s.bbox_score, o.overture_id, ST_Area(ST_Intersection(s.geom_m, o.geom_m)) AS inter_m2, ST_Area(s.geom_m) AS sam3_m2, ST_Area(o.geom_m) AS overture_m2, ST_Area(ST_Intersection(s.geom_m, o.geom_m)) / NULLIF(ST_Area(s.geom_m) + ST_Area(o.geom_m) - ST_Area(ST_Intersection(s.geom_m, o.geom_m)), 0) AS iou FROM sam3_roofs s JOIN overture_bldgs o ON ST_Intersects(s.geom_4326, o.geom_4326) Intersection-over-union (IoU) is the area shared by two polygons divided by their combined area. A value of 1.0 is a perfect match; 0.0 means no overlap. The ST_Intersects predicate runs on the lon/lat geometries, where Sedona’s spatial join planner has the column statistics it needs to be efficient; the ST_Area and ST_Intersection calls run on the UTM-projected geometries, where the resulting numbers are in square meters and meaningful. The query returns 207,109 candidate matched pairs and is cached for the rest of the analysis. Resolution and metrics. Sometimes a single SAM3 polygon covers several Overture buildings, for example when SAM3 segments a multi-roof complex as one shape. Other times, several SAM3 polygons land on the same Overture building. To keep the comparison clean, the notebook keeps only the best-matching pair on each side. The remaining cells then compute the numbers the rest of the post relies on: Recall. What fraction of Overture buildings has a matching SAM3 detection of roughly the right shape. Precision. What fraction of SAM3 detections lines up with a known Overture building. Calibration. Whether SAM3’s confidence score is a reliable signal of how well-shaped each detection actually is. Aggregate area. How the total roof area SAM3 found across the county compares to the total building footprint area Overture has. A final cell renders five high-IoU and five low-IoU matched pairs on aerial imagery for visual spot-checking, so I could see with my own eyes where SAM3 and Overture agree and where they don’t. Example results comparing Overture building footprints (yellow) and SAM3 roof detections (blue). Top: a high quality result. Center: SAM3 failed to segment the roof in this building. Bottom: SAM3 segments part of the roof, but not the whole footprint. SAM3 Building Detection Accuracy: Recall, Precision, and IoU Results I wasn’t sure how best to assess the results. So I asked Claude to help with the analysis and here is a summary: The overall correlation is very high. SAM3 and Overture report total roof area within 7% of each other across this county. That level of agreement says they are measuring the same physical objects, not coincidentally landing on the same total. SAM3’s confidence score correlates to the accuracy of the polygon. SAM3 detections with high confidence scores have polygons that line up well with the real buildings, while lower confidence detections have less accurate shapes. That means we can use the confidence score as a reliable filter. Many of SAM3’s “false positives” are real buildings Overture missed. The assistant pointed out that many of the SAM3 detections without an Overture match are actual rural houses and outbuildings visible on aerial imagery. So the real precision is likely higher than 75%. When SAM3 finds a building, it usually gets the shape right. Only about 12 percentage points separate “found something on this building” from “found roughly the right shape on this building.” That means SAM3 is not just placing a point on the map, it is accurately outlining the building roofs. These results are impressive! With a single text prompt (”roofs”), I was able to produce detections that match a curated, multi-source reference dataset to within 7% on total roof area, found the right shape on three-quarters of known buildings, and carried a confidence score an application can actually trust. Even though SAM3 was not fine-tuned for roof detection, the resulting output would be operationally useful for many use cases. Using the Spatial AI Coding Assistant to Evaluate Computer Vision ML Models A few hours after I started, I had an initial assessment on the quality of the SAM3 detections. And I had a notebook with reproducible results that anyone can rerun in minutes. The assistant does not replace the role of the data scientist, but accelerates initial spatial and statistical analysis. And it did it while surfacing important caveats from the start. Try It Yourself Sign up for Wherobots Cloud and connect the Spatial AI Assistant to your own data. Read the prior RasterFlow + SAM3 post for the upstream half of this workflow. Sign up for the RasterFlow private preview to try out SAM3 today! Try the Spatial AI Coding Assistant GET STARTED Key takeawaysA prior RasterFlow + SAM3 job built a 133 GB NAIP mosaic of Marion County, Oregon, ran text prompts across eight classes, and produced about one million detection polygons, including roughly 312,000 building roofs. This post evaluates those roof detections against Overture Maps buildings in the Wherobots Hub.The evaluation was designed and executed in four prompts with the Spatial AI Assistant inside Claude, using the Wherobots MCP server. The assistant returned a 24-cell notebook. Area math uses UTM zone 10N (EPSG:32610), not EPSG:4326. The AOI is the official Marion County admin boundary from wkls, not a bounding box.Inside the official county polygon: 261,414 SAM3 roofs vs 146,642 Overture buildings. The spatial join produced 207,109 candidate matched pairs. SAM3 and Overture total roof area agree within 7% across the county.Caveats baked into the analysis: roofs are not footprints; Overture is not ground truth in rural areas and likely misses buildings; the 0.5 confidence cutoff was already applied in SAM3 outputs, limiting precision-recall to the upper half of the confidence range. Many SAM3 'false positives' are real rural buildings Overture missed, so real precision is likely higher than 75%.
Change Detection Using AlphaEarth Foundations (Part 2) Posted on May 6, 2026October 4, 2026 by Ben Pruden Author: Len Strnad AlphaEarth Foundations embeddings gave us an initial view of temporal change in our earlier post on agricultural dynamics, but that first pass also exposed a limitation: raw embedding distances do not live on a consistent scale across pixels or land-cover types. As a result, it is hard to compare how unusual a given year looks from place to place, even when the relative signal is informative. In this follow-up, we ask a more targeted question: can we score each annual embedding by how much it stands out from the rest of its local time series? To do that, we compute leave-one-out (LOO) medoid scores in embedding space and apply robust z-score scaling through time for each pixel. This gives us a more stable and interpretable way to identify unusual years across a short annual record. As in the earlier AlphaEarth Foundations agriculture post, the goal is not to present a definitive change-detection benchmark. Instead, we show a practical workflow for surfacing unusual temporal states in AEF embeddings. We run that workflow against the global AEF Zarr mosaic hosted on Source Cooperative, build a multiscale output for exploration, and inspect several examples around Boulder, Colorado. Global Zarr AEF Mosaic For this follow-up we use the global AlphaEarth Foundations Zarr mosaic hosted on Source Cooperative. Thanks to Taylor Geospatial and Source Cooperative for making the dataset available in a form that is easy to access lazily in a single python function call! The mosaic stores annual AEF embeddings at 10-meter resolution in EPSG:4326, with nine temporal snapshots from 2017 through 2025. Working from one global store keeps the workflow simple: we can subset the region of interest, compute scores, and publish a derived multiscale product without assembling separate local inputs. import xarray as xr ds = xr.open_zarr( "s3://us-west-2.opendata.source.coop/tge-labs/aef-mosaic/", consolidated=False, storage_options={"anon": True}, ) ds /Users/len/code/wherobots-notebook-blogs/.pixi/envs/default/lib/python3.14/site-packages/zarr/core/group.py:3559: ZarrUserWarning: Object at README.md is not recognized as a component of a Zarr hierarchy. warnings.warn( /Users/len/code/wherobots-notebook-blogs/.pixi/envs/default/lib/python3.14/site-packages/zarr/core/group.py:3559: ZarrUserWarning: Object at .checkpoint is not recognized as a component of a Zarr hierarchy. warnings.warn( <xarray.Dataset> Size: 4PB Dimensions: (time: 9, band: 64, y: 1859584, x: 4009984) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) object 512B 'A00' 'A01' 'A02' 'A03' ... 'A61' 'A62' 'A63' * y (y) float64 15MB 83.69 83.69 83.69 ... -83.36 -83.36 -83.36 * x (x) float64 32MB -180.0 -180.0 -180.0 ... 180.2 180.2 180.2 Data variables: embeddings (time, band, y, x) int8 4PB dask.array<chunksize=(1, 64, 256, 256), meta=np.ndarray> Attributes: (12/15) proj:code: EPSG:4326 spatial:dimensions: ['y', 'x'] spatial:transform: [8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983... spatial:transform_type: affine spatial:bbox: [-180.0, -83.36280346631479, 180.2213438735178, ... spatial:shape: [1859584, 4009984] ... ... geoemb:model: https://developers.google.com/earth-engine/datas... geoemb:source_data: https://source.coop/tge-labs/aef/v1/annual/ geoemb:data_type: int8 geoemb:gsd: 8.983111749910169e-05 geoemb:quantization: {'method': 'signed_square', 'original_dtype': 'f... zarr_conventions: [{'uuid': 'f17cb550-5864-4468-aeb7-f3180cfb622f'...xarray.DatasetDimensions:time: 9band: 64y: 1859584x: 4009984Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)object‘A00’ ‘A01’ ‘A02’ … ‘A62’ ‘A63’_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array(['A00', 'A01', 'A02', 'A03', 'A04', 'A05', 'A06', 'A07', 'A08', 'A09', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'A16', 'A17', 'A18', 'A19', 'A20', 'A21', 'A22', 'A23', 'A24', 'A25', 'A26', 'A27', 'A28', 'A29', 'A30', 'A31', 'A32', 'A33', 'A34', 'A35', 'A36', 'A37', 'A38', 'A39', 'A40', 'A41', 'A42', 'A43', 'A44', 'A45', 'A46', 'A47', 'A48', 'A49', 'A50', 'A51', 'A52', 'A53', 'A54', 'A55', 'A56', 'A57', 'A58', 'A59', 'A60', 'A61', 'A62', 'A63'], dtype=object)y(y)float6483.69 83.69 83.69 … -83.36 -83.36_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([ 83.68566 , 83.685571, 83.685481, ..., -83.362579, -83.362669, -83.362759], shape=(1859584,))x(x)float64-180.0 -180.0 … 180.2 180.2_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([-179.999955, -179.999865, -179.999775, ..., 180.221119, 180.221209, 180.221299], shape=(4009984,))Data variables: (1)embeddings(time, band, y, x)int8dask.array<chunksize=(1, 64, 256, 256), meta=np.ndarray>_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’} Array Chunk Bytes 3.81 PiB 4.00 MiB Shape (9, 64, 1859584, 4009984) (1, 64, 256, 256) Dask graph 1024049664 chunks in 2 graph layers Data type int8 numpy.ndarray 9 1 4009984 1859584 64 Attributes: (15)proj:code :EPSG:4326spatial:dimensions :[‘y’, ‘x’]spatial:transform :[8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983111749910169e-05, 83.68570533713473]spatial:transform_type :affinespatial:bbox :[-180.0, -83.36280346631479, 180.2213438735178, 83.68570533713473]spatial:shape :[1859584, 4009984]spatial:registration :pixelgeoemb:type :pixelgeoemb:dimensions :64geoemb:model :https://developers.google.com/earth-engine/datasets/catalog/GOOGLE_SATELLITE_EMBEDDING_V1_ANNUALgeoemb:source_data :https://source.coop/tge-labs/aef/v1/annual/geoemb:data_type :int8geoemb:gsd :8.983111749910169e-05geoemb:quantization :{‘method’: ‘signed_square’, ‘original_dtype’: ‘float32’, ‘quantized_dtype’: ‘int8’, ‘formula’: ‘(x / 127.5) ** 2 * sign(x)’, ‘valid_range’: [-127, 127], ‘nodata’: -128}zarr_conventions :[{‘uuid’: ‘f17cb550-5864-4468-aeb7-f3180cfb622f’, ‘name’: ‘proj:’, ‘description’: ‘Coordinate reference system information for geospatial data’, ‘spec_url’: ‘https://github.com/zarr-experimental/geo-proj/blob/v1/README.md’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json’}, {‘uuid’: ‘689b58e2-cf7b-45e0-9fff-9cfc0883d6b4’, ‘name’: ‘spatial:’, ‘description’: ‘Spatial coordinate information’, ‘spec_url’: ‘https://github.com/zarr-conventions/spatial/blob/v1/README.md’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json’}, {‘uuid’: ’61c12cc5-0e28-4056-999a-480cf3fb7e4c’, ‘name’: ‘geoemb:’, ‘description’: ‘Geo-embeddings metadata for machine learning embeddings stored in Zarr’, ‘spec_url’: ‘https://github.com/geo-embeddings/embeddings-zarr-convention/tree/add-convention’}] At first glance, 3.81 PiB and 1,024,049,664 chunks in S3 sounds impractically large. For this demo, though, a few details keep the workflow manageable: only chunks that intersect land are present the dataset is sharded into nine spatial chunks, which reduces the effective object count we subset Colorado rather than operating on the full global mosaic RasterFlow Tools Used RasterFlow is Wherobots’ serverless inference engine for Earth Observation data. It builds inference-ready mosaics from remote sensing data and runs distributed workflows at scale. This notebook uses two RasterFlow capabilities: run_mosaics_change on the RasterFlow Remote client for computing difference and LOO medoid scores over time build_zarr_multiscales for producing a visualization-ready Zarr mosaic Change Detection with RasterFlow Remote For this run, we call run_mosaics_change on the RasterFlow Remote client. The method takes an input Zarr mosaic and writes out a derived Zarr mosaic containing one or more temporal change scores. Here we compute both difference-over-time and LOO medoid scores in AEF embedding space, with robust z-score normalization applied across time for each pixel. The snippet below matches the current remote-client API used for this Colorado example. from affine import Affine from rasterio.transform import rowcol # construct a bounding box selector for Colorado transform = Affine(*ds.attrs["spatial:transform"]) row_min, col_min = rowcol(transform, -109.060253, 41.003444) row_max, col_max = rowcol(transform, -102.041524, 37.000232) selector = {"x": [col_min, col_max], "y": [row_min, row_max]} selector {'x': [np.int32(789701), np.int32(867833)], 'y': [np.int32(475138), np.int32(519702)]} from rasterflow_remote import DistanceMetricEnum, RasterflowClient, TemporalScoreMethodEnum client = RasterflowClient() result = client.run_mosaics_change( store="s3://us-west-2.opendata.source.coop/tge-labs/aef-mosaic/", score_methods=[ TemporalScoreMethodEnum.DIFFERENCE, TemporalScoreMethodEnum.LOO_MEDOID, ], distance_metric=DistanceMetricEnum.ALPHA_EARTH_COSINE, data_var="embeddings", robust_time_zscore=True, selector=selector, ) LOO Medoid Scores for Small Samples To measure how unusual a given annual embedding is relative to its temporal context, we use a leave-one-out medoid (LOO medoid) score in embedding space. This is well suited to the AEF setting here, where we only have nine annual observations and want a reference that is less sensitive to outliers than a simple average. Given a time series of embeddings {e1,…,eT}\{e_1, \dots, e_T\}, the LOO medoid score at time tkt_k compares eke_k to a representative embedding computed from the remaining observations: E−k={ej∣j≠k}. E_{-k} = \{e_j \mid j \neq k\}. We define that representative as the medoid of E−kE_{-k}, i.e. the embedding that minimizes total distance to all others: emedoid(−k)=argminei∈E−k∑ej∈E−kd(ei,ej). e_{\text{medoid}}^{(-k)} = \arg\min_{e_i \in E_{-k}} \sum_{e_j \in E_{-k}} d(e_i, e_j). The LOO medoid score is then: LOOmedoid(tk)=d(ek,emedoid(−k)), \mathrm{LOO}_{\mathrm{medoid}}(t_k) = d\left(e_k, e_{\text{medoid}}^{(-k)}\right), where d(⋅,⋅)d(\cdot, \cdot) is a distance metric (e.g., Euclidean or cosine distance). Interpretation: LOOmedoid(tk)\mathrm{LOO}_{\mathrm{medoid}}(t_k) measures how far the embedding at time tkt_k is from the medoid of the remaining time series. Larger values indicate that the year-specific embedding is less consistent with the rest of the temporal sequence, suggesting a potential change or anomalous state with respect to all other years. Results Using the workflow above, we compute two normalized change layers across the nine-year Colorado subset of the AEF mosaic: a difference-over-time score and a LOO medoid score. The intermediate result below is a Zarr dataset. You may notice: 9 time steps, corresponding to the annual AEF embeddings from 2017 through 2025 2 score bands, one for consecutive difference and one for LOO medoid deviation ~235Gb of data ds = xr.open_zarr( "s3://sandbox-wherobots-mosaics-tmp/rasterflow/rasterflow/development/rbz2h49wvpjt7h4nzq5g/76e986b947cc.zarr" ) ds <xarray.Dataset> Size: 253GB Dimensions: (time: 9, band: 2, y: 44800, x: 78336) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) object 16B 'difference_alpha_earth_cosine_distance_rob... * y (y) float64 358kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 * x (x) float64 627kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 Data variables: embeddings (time, band, y, x) float32 253GB dask.array<chunksize=(1, 1, 256, 256), meta=np.ndarray> Attributes: (12/15) proj:code: EPSG:4326 spatial:dimensions: ['y', 'x'] spatial:transform: [8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983... spatial:transform_type: affine spatial:bbox: [-180.0, -83.36280346631479, 180.2213438735178, ... spatial:shape: [1859584, 4009984] ... ... geoemb:model: https://developers.google.com/earth-engine/datas... geoemb:source_data: https://source.coop/tge-labs/aef/v1/annual/ geoemb:data_type: int8 geoemb:gsd: 8.983111749910169e-05 geoemb:quantization: {'method': 'signed_square', 'original_dtype': 'f... zarr_conventions: [{'uuid': 'f17cb550-5864-4468-aeb7-f3180cfb622f'...xarray.DatasetDimensions:time: 9band: 2y: 44800x: 78336Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)object‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore', 'loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'], dtype=object)y(y)float6441.0 41.0 41.0 … 36.98 36.98_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([41.003663, 41.003573, 41.003483, ..., 36.979498, 36.979408, 36.979318], shape=(44800,))x(x)float64-109.1 -109.1 … -102.0 -102.0_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([-109.077928, -109.077839, -109.077749, ..., -102.041188, -102.041098, -102.041008], shape=(78336,))Data variables: (1)embeddings(time, band, y, x)float32dask.array<chunksize=(1, 1, 256, 256), meta=np.ndarray>_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’} Array Chunk Bytes 235.33 GiB 256.00 kiB Shape (9, 2, 44800, 78336) (1, 1, 256, 256) Dask graph 963900 chunks in 2 graph layers Data type float32 numpy.ndarray 9 1 78336 44800 2 Attributes: (15)proj:code :EPSG:4326spatial:dimensions :[‘y’, ‘x’]spatial:transform :[8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983111749910169e-05, 83.68570533713473]spatial:transform_type :affinespatial:bbox :[-180.0, -83.36280346631479, 180.2213438735178, 83.68570533713473]spatial:shape :[1859584, 4009984]spatial:registration :pixelgeoemb:type :pixelgeoemb:dimensions :64geoemb:model :https://developers.google.com/earth-engine/datasets/catalog/GOOGLE_SATELLITE_EMBEDDING_V1_ANNUALgeoemb:source_data :https://source.coop/tge-labs/aef/v1/annual/geoemb:data_type :int8geoemb:gsd :8.983111749910169e-05geoemb:quantization :{‘method’: ‘signed_square’, ‘original_dtype’: ‘float32’, ‘quantized_dtype’: ‘int8’, ‘formula’: ‘(x / 127.5) ** 2 * sign(x)’, ‘valid_range’: [-127, 127], ‘nodata’: -128}zarr_conventions :[{‘uuid’: ‘f17cb550-5864-4468-aeb7-f3180cfb622f’, ‘name’: ‘proj:’, ‘description’: ‘Coordinate reference system information for geospatial data’, ‘spec_url’: ‘https://github.com/zarr-experimental/geo-proj/blob/v1/README.md’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json’}, {‘uuid’: ‘689b58e2-cf7b-45e0-9fff-9cfc0883d6b4’, ‘name’: ‘spatial:’, ‘description’: ‘Spatial coordinate information’, ‘spec_url’: ‘https://github.com/zarr-conventions/spatial/blob/v1/README.md’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json’}, {‘uuid’: ’61c12cc5-0e28-4056-999a-480cf3fb7e4c’, ‘name’: ‘geoemb:’, ‘description’: ‘Geo-embeddings metadata for machine learning embeddings stored in Zarr’, ‘spec_url’: ‘https://github.com/geo-embeddings/embeddings-zarr-convention/tree/add-convention’}] Visualization To make the result easier to explore, we run the build_zarr_multiscales function to create a multiscale Zarr mosaic optimized for interactive viewing and analysis, then publish it to a public bucket for anyone to explore. xr.open_datatree("s3://wherobots-examples/rasterflow/mosaics/co-aef-change.zarr") <xarray.DataTree> Group: / │ Attributes: │ zarr_conventions: [{'uuid': 'd35379db-88df-4056-af3a-620245f8e347'... │ multiscales: {'layout': [{'asset': '0', 'transform': {'scale'... │ proj:code: EPSG:4326 │ proj:wkt2: GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System... │ spatial:dimensions: ['y', 'x'] │ spatial:registration: pixel │ spatial:transform_type: affine │ spatial:shape: [44800, 78336] │ spatial:transform: [8.983111749216732e-05, 0.0, -109.07797340998921... │ spatial:bbox: [-109.07797340998921, 36.97927342911413, -102.04... ├── Group: /0 │ Dimensions: (time: 9, band: 2, y: 44800, x: 78336) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 358kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 627kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 253GB ... │ spatial_ref int32 4B ... ├── Group: /1 │ Dimensions: (time: 9, band: 2, y: 22400, x: 39168) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 179kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 313kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 63GB ... │ spatial_ref int32 4B ... ├── Group: /2 │ Dimensions: (time: 9, band: 2, y: 11200, x: 19584) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 90kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 157kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 16GB ... │ spatial_ref int32 4B ... ├── Group: /3 │ Dimensions: (time: 9, band: 2, y: 5600, x: 9792) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 45kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 78kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 4GB ... │ spatial_ref int32 4B ... ├── Group: /4 │ Dimensions: (time: 9, band: 2, y: 2800, x: 4896) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 22kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 39kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 987MB ... │ spatial_ref int32 4B ... ├── Group: /5 │ Dimensions: (time: 9, band: 2, y: 1400, x: 2448) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 11kB 41.0 41.0 41.0 40.99 ... 36.99 36.98 36.98 │ * x (x) float64 20kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 247MB ... │ spatial_ref int32 4B ... ├── Group: /6 │ Dimensions: (time: 9, band: 2, y: 700, x: 1224) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 6kB 41.0 41.0 40.99 40.98 ... 36.99 36.99 36.98 │ * x (x) float64 10kB -109.1 -109.1 -109.1 ... -102.1 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 62MB ... │ spatial_ref int32 4B ... ├── Group: /7 │ Dimensions: (time: 9, band: 2, y: 350, x: 612) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 3kB 41.0 40.99 40.97 40.96 ... 37.01 37.0 36.99 │ * x (x) float64 5kB -109.1 -109.1 -109.0 ... -102.1 -102.1 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 15MB ... │ spatial_ref int32 4B ... ├── Group: /8 │ Dimensions: (time: 9, band: 2, y: 175, x: 306) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 1kB 40.99 40.97 40.95 40.92 ... 37.04 37.01 36.99 │ * x (x) float64 2kB -109.1 -109.0 -109.0 ... -102.1 -102.1 -102.1 │ Data variables: │ embeddings (time, band, y, x) float32 4MB ... │ spatial_ref int32 4B ... └── Group: /9 Dimensions: (time: 9, band: 2, y: 88, x: 153) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... * y (y) float64 704B 40.98 40.93 40.89 40.84 ... 37.07 37.03 36.98 * x (x) float64 1kB -109.1 -109.0 -109.0 ... -102.2 -102.1 -102.1 Data variables: embeddings (time, band, y, x) float32 969kB ... spatial_ref int32 4B ...xarray.DataTree/0(6)Dimensions:time: 9band: 2y: 44800x: 78336Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.003663, 41.003573, 41.003483, ..., 36.979498, 36.979408, 36.979318],shape=(44800,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.077928, -109.077839, -109.077749, ..., -102.041188, -102.041098,-102.041008], shape=(78336,))Data variables: (2)embeddings(time, band, y, x)float32…raster:histogram :[{‘count’: 256, ‘min’: -453.6422424316406, ‘max’: 1552.254150390625, ‘buckets’: [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1, 0, 0, 3, 0, 4, 5, 7, 2, 4, 5, 2, 9, 12, 19, 15, 23, 24, 28, 45, 67, 80, 103, 167, 301, 411, 731, 1247, 2265, 4825, 10898, 30131, 105677, 586428, 9241259, 21155882067, 6458720426, 141842195, 20581637, 6126534, 2475655, 1188362, 635136, 369947, 226262, 143658, 92272, 62053, 42110, 30694, 21947, 16372, 12457, 9650, 7272, 5705, 4679, 3559, 2871, 2342, 1962, 1666, 1430, 1127, 903, 790, 680, 636, 482, 432, 339, 332, 295, 258, 210, 170, 135, 170, 165, 124, 93, 89, 63, 81, 76, 55, 76, 63, 38, 53, 44, 31, 28, 19, 40, 24, 17, 25, 27, 19, 15, 17, 19, 18, 10, 10, 10, 4, 10, 7, 7, 4, 5, 8, 3, 6, 3, 1, 3, 6, 1, 4, 4, 0, 4, 2, 9, 3, 0, 3, 1, 2, 0, 8, 3, 1, 1, 0, 8, 0, 3, 4, 5, 0, 0, 0, 3, 2, 2, 2, 1, 3, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], ‘index’: {‘band’: 0}}, {‘count’: 256, ‘min’: -2419.623046875, ‘max’: 16685.6171875, ‘buckets’: [2, 0, 0, 0, 3, 0, 2, 0, 1, 3, 4, 2, 1, 1, 2, 1, 2, 6, 2, 6, 7, 10, 11, 12, 21, 53, 83, 165, 461, 1381, 7820, 331440, 31261242551, 10735198, 734657, 158052, 53459, 22328, 11132, 6140, 3758, 2266, 1447, 1093, 731, 612, 434, 326, 236, 189, 171, 139, 93, 106, 91, 63, 54, 53, 33, 21, 16, 15, 25, 14, 18, 22, 15, 5, 17, 14, 14, 11, 5, 6, 11, 1, 12, 3, 5, 0, 2, 3, 1, 3, 0, 0, 2, 7, 1, 0, 5, 0, 2, 0, 0, 1, 3, 0, 2, 3, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], ‘index’: {‘band’: 1}}][63170150400 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 8.983111749216732e-05 0.0 41.00370749308155 0.0 -8.983111749927275e-05[1 values with dtype=int32]/1(6)Dimensions:time: 9band: 2y: 22400x: 39168Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.003618, 41.003438, 41.003258, ..., 36.979723, 36.979543, 36.979363],shape=(22400,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.077884, -109.077704, -109.077524, ..., -102.041412, -102.041232,-102.041053], shape=(39168,))Data variables: (2)embeddings(time, band, y, x)float32…[15792537600 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.00017966223498433465 0.0 41.00370749308155 0.0 -0.0001796622349985455[1 values with dtype=int32]/2(6)Dimensions:time: 9band: 2y: 11200x: 19584Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.003528, 41.003169, 41.002809, ..., 36.980172, 36.979812, 36.979453],shape=(11200,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.077794, -109.077434, -109.077075, ..., -102.041861, -102.041502,-102.041143], shape=(19584,))Data variables: (2)embeddings(time, band, y, x)float32…[3948134400 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.0003593244699686693 0.0 41.00370749308155 0.0 -0.000359324469997091[1 values with dtype=int32]/3(6)Dimensions:time: 9band: 2y: 5600x: 9792Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.003348, 41.00263 , 41.001911, ..., 36.98107 , 36.980351, 36.979633],shape=(5600,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.077614, -109.076895, -109.076177, ..., -102.04276 , -102.042041,-102.041322], shape=(9792,))Data variables: (2)embeddings(time, band, y, x)float32…[987033600 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.0007186489399373386 0.0 41.00370749308155 0.0 -0.000718648939994182[1 values with dtype=int32]/4(6)Dimensions:time: 9band: 2y: 2800x: 4896Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.002989, 41.001552, 41.000114, ..., 36.982867, 36.981429, 36.979992],shape=(2800,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.077255, -109.075817, -109.07438 , ..., -102.044556, -102.043119,-102.041682], shape=(4896,))Data variables: (2)embeddings(time, band, y, x)float32…[246758400 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.0014372978798746772 0.0 41.00370749308155 0.0 -0.001437297879988364[1 values with dtype=int32]/5(6)Dimensions:time: 9band: 2y: 1400x: 2448Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 41.0 … 36.98 36.98units :metersstandard_name :projection_y_coordinatearray([41.00227 , 40.999396, 40.996521, ..., 36.98646 , 36.983585, 36.980711],shape=(1400,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.076536, -109.073662, -109.070787, ..., -102.048149, -102.045275,-102.0424 ], shape=(2448,))Data variables: (2)embeddings(time, band, y, x)float32…[61689600 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.0028745957597493543 0.0 41.00370749308155 0.0 -0.002874595759976728[1 values with dtype=int32]/6(6)Dimensions:time: 9band: 2y: 700x: 1224Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 41.0 40.99 … 36.99 36.98units :metersstandard_name :projection_y_coordinatearray([41.000833, 40.995084, 40.989335, ..., 36.993646, 36.987897, 36.982148],shape=(700,))x(x)float64-109.1 -109.1 … -102.0 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.075099, -109.06935 , -109.0636 , ..., -102.055336, -102.049587,-102.043838], shape=(1224,))Data variables: (2)embeddings(time, band, y, x)float32…[15422400 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.005749191519498709 0.0 41.00370749308155 0.0 -0.005749191519953456[1 values with dtype=int32]/7(6)Dimensions:time: 9band: 2y: 350x: 612Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6441.0 40.99 40.97 … 37.0 36.99units :metersstandard_name :projection_y_coordinatearray([40.997958, 40.98646 , 40.974962, ..., 37.008019, 36.996521, 36.985023],shape=(350,))x(x)float64-109.1 -109.1 … -102.1 -102.0units :metersstandard_name :projection_x_coordinatearray([-109.072224, -109.060726, -109.049227, ..., -102.069709, -102.058211,-102.046712], shape=(612,))Data variables: (2)embeddings(time, band, y, x)float32…[3855600 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.011498383038997417 0.0 41.00370749308155 0.0 -0.011498383039906912[1 values with dtype=int32]/8(6)Dimensions:time: 9band: 2y: 175x: 306Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6440.99 40.97 40.95 … 37.01 36.99units :metersstandard_name :projection_y_coordinatearray([40.992209, 40.969212, 40.946216, 40.923219, 40.900222, 40.877225,40.854229, 40.831232, 40.808235, 40.785238, 40.762241, 40.739245,40.716248, 40.693251, 40.670254, 40.647258, 40.624261, 40.601264,40.578267, 40.555271, 40.532274, 40.509277, 40.48628 , 40.463283,40.440287, 40.41729 , 40.394293, 40.371296, 40.3483 , 40.325303,40.302306, 40.279309, 40.256313, 40.233316, 40.210319, 40.187322,40.164326, 40.141329, 40.118332, 40.095335, 40.072338, 40.049342,40.026345, 40.003348, 39.980351, 39.957355, 39.934358, 39.911361,39.888364, 39.865368, 39.842371, 39.819374, 39.796377, 39.773381,39.750384, 39.727387, 39.70439 , 39.681393, 39.658397, 39.6354 ,39.612403, 39.589406, 39.56641 , 39.543413, 39.520416, 39.497419,39.474423, 39.451426, 39.428429, 39.405432, 39.382435, 39.359439,39.336442, 39.313445, 39.290448, 39.267452, 39.244455, 39.221458,39.198461, 39.175465, 39.152468, 39.129471, 39.106474, 39.083478,39.060481, 39.037484, 39.014487, 38.99149 , 38.968494, 38.945497,38.9225 , 38.899503, 38.876507, 38.85351 , 38.830513, 38.807516,38.78452 , 38.761523, 38.738526, 38.715529, 38.692533, 38.669536,38.646539, 38.623542, 38.600545, 38.577549, 38.554552, 38.531555,38.508558, 38.485562, 38.462565, 38.439568, 38.416571, 38.393575,38.370578, 38.347581, 38.324584, 38.301587, 38.278591, 38.255594,38.232597, 38.2096 , 38.186604, 38.163607, 38.14061 , 38.117613,38.094617, 38.07162 , 38.048623, 38.025626, 38.00263 , 37.979633,37.956636, 37.933639, 37.910642, 37.887646, 37.864649, 37.841652,37.818655, 37.795659, 37.772662, 37.749665, 37.726668, 37.703672,37.680675, 37.657678, 37.634681, 37.611684, 37.588688, 37.565691,37.542694, 37.519697, 37.496701, 37.473704, 37.450707, 37.42771 ,37.404714, 37.381717, 37.35872 , 37.335723, 37.312727, 37.28973 ,37.266733, 37.243736, 37.220739, 37.197743, 37.174746, 37.151749,37.128752, 37.105756, 37.082759, 37.059762, 37.036765, 37.013769,36.990772])x(x)float64-109.1 -109.0 … -102.1 -102.1units :metersstandard_name :projection_x_coordinatearray([-109.066475, -109.043478, -109.020481, ..., -102.098455, -102.075458,-102.052461], shape=(306,))Data variables: (2)embeddings(time, band, y, x)float32…[963900 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.022996766077994835 0.0 41.00370749308155 0.0 -0.022996766079813824[1 values with dtype=int32]/9(6)Dimensions:time: 9band: 2y: 88x: 153Coordinates: (4)time(time)int322017 2018 2019 … 2023 2024 2025_zarrs :{‘description’: ‘This array was created with zarrs’, ‘repository’: ‘https://github.com/zarrs/zarrs’, ‘version’: ‘0.23.0’}array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)band(band)StringDType()‘difference_alpha_earth_cosine_d…array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())y(y)float6440.98 40.93 40.89 … 37.03 36.98units :metersstandard_name :projection_y_coordinatearray([40.980711, 40.934717, 40.888724, 40.84273 , 40.796737, 40.750743,40.70475 , 40.658756, 40.612762, 40.566769, 40.520775, 40.474782,40.428788, 40.382795, 40.336801, 40.290808, 40.244814, 40.198821,40.152827, 40.106834, 40.06084 , 40.014847, 39.968853, 39.922859,39.876866, 39.830872, 39.784879, 39.738885, 39.692892, 39.646898,39.600905, 39.554911, 39.508918, 39.462924, 39.416931, 39.370937,39.324944, 39.27895 , 39.232957, 39.186963, 39.140969, 39.094976,39.048982, 39.002989, 38.956995, 38.911002, 38.865008, 38.819015,38.773021, 38.727028, 38.681034, 38.635041, 38.589047, 38.543054,38.49706 , 38.451066, 38.405073, 38.359079, 38.313086, 38.267092,38.221099, 38.175105, 38.129112, 38.083118, 38.037125, 37.991131,37.945138, 37.899144, 37.853151, 37.807157, 37.761163, 37.71517 ,37.669176, 37.623183, 37.577189, 37.531196, 37.485202, 37.439209,37.393215, 37.347222, 37.301228, 37.255235, 37.209241, 37.163248,37.117254, 37.07126 , 37.025267, 36.979273])x(x)float64-109.1 -109.0 … -102.1 -102.1units :metersstandard_name :projection_x_coordinatearray([-109.054977, -109.008983, -108.96299 , -108.916996, -108.871003,-108.825009, -108.779015, -108.733022, -108.687028, -108.641035,-108.595041, -108.549048, -108.503054, -108.457061, -108.411067,-108.365074, -108.31908 , -108.273087, -108.227093, -108.1811 ,-108.135106, -108.089112, -108.043119, -107.997125, -107.951132,-107.905138, -107.859145, -107.813151, -107.767158, -107.721164,-107.675171, -107.629177, -107.583184, -107.53719 , -107.491197,-107.445203, -107.399209, -107.353216, -107.307222, -107.261229,-107.215235, -107.169242, -107.123248, -107.077255, -107.031261,-106.985268, -106.939274, -106.893281, -106.847287, -106.801294,-106.7553 , -106.709307, -106.663313, -106.617319, -106.571326,-106.525332, -106.479339, -106.433345, -106.387352, -106.341358,-106.295365, -106.249371, -106.203378, -106.157384, -106.111391,-106.065397, -106.019404, -105.97341 , -105.927416, -105.881423,-105.835429, -105.789436, -105.743442, -105.697449, -105.651455,-105.605462, -105.559468, -105.513475, -105.467481, -105.421488,-105.375494, -105.329501, -105.283507, -105.237513, -105.19152 ,-105.145526, -105.099533, -105.053539, -105.007546, -104.961552,-104.915559, -104.869565, -104.823572, -104.777578, -104.731585,-104.685591, -104.639598, -104.593604, -104.54761 , -104.501617,-104.455623, -104.40963 , -104.363636, -104.317643, -104.271649,-104.225656, -104.179662, -104.133669, -104.087675, -104.041682,-103.995688, -103.949695, -103.903701, -103.857708, -103.811714,-103.76572 , -103.719727, -103.673733, -103.62774 , -103.581746,-103.535753, -103.489759, -103.443766, -103.397772, -103.351779,-103.305785, -103.259792, -103.213798, -103.167805, -103.121811,-103.075817, -103.029824, -102.98383 , -102.937837, -102.891843,-102.84585 , -102.799856, -102.753863, -102.707869, -102.661876,-102.615882, -102.569889, -102.523895, -102.477902, -102.431908,-102.385914, -102.339921, -102.293927, -102.247934, -102.20194 ,-102.155947, -102.109953, -102.06396 ])Data variables: (2)embeddings(time, band, y, x)float32…[242352 values with dtype=float32]spatial_ref()int32…crs_wkt :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]GeoTransform :-109.07797340998921 0.04599353215598967 0.0 41.00370749308155 0.0 -0.04599353215962765[1 values with dtype=int32]Attributes: (10)zarr_conventions :[{‘uuid’: ‘d35379db-88df-4056-af3a-620245f8e347’, ‘name’: ‘multiscales’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json’}, {‘uuid’: ‘f17cb550-5864-4468-aeb7-f3180cfb622f’, ‘name’: ‘proj:’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json’}, {‘uuid’: ‘689b58e2-cf7b-45e0-9fff-9cfc0883d6b4’, ‘name’: ‘spatial:’, ‘schema_url’: ‘https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json’}]multiscales :{‘layout’: [{‘asset’: ‘0’, ‘transform’: {‘scale’: [1.0, 1.0], ‘translation’: [0.0, 0.0]}, ‘spatial:shape’: [44800, 78336], ‘spatial:transform’: [8.983111749216732e-05, 0.0, -109.07797340998921, 0.0, -8.983111749927275e-05, 41.00370749308155]}, {‘asset’: ‘1’, ‘transform’: {‘scale’: [2.0, 2.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘0’, ‘spatial:shape’: [22400, 39168], ‘spatial:transform’: [0.00017966223498433465, 0.0, -109.07797340998921, 0.0, -0.0001796622349985455, 41.00370749308155]}, {‘asset’: ‘2’, ‘transform’: {‘scale’: [4.0, 4.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘1’, ‘spatial:shape’: [11200, 19584], ‘spatial:transform’: [0.0003593244699686693, 0.0, -109.07797340998921, 0.0, -0.000359324469997091, 41.00370749308155]}, {‘asset’: ‘3’, ‘transform’: {‘scale’: [8.0, 8.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘2’, ‘spatial:shape’: [5600, 9792], ‘spatial:transform’: [0.0007186489399373386, 0.0, -109.07797340998921, 0.0, -0.000718648939994182, 41.00370749308155]}, {‘asset’: ‘4’, ‘transform’: {‘scale’: [16.0, 16.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘3’, ‘spatial:shape’: [2800, 4896], ‘spatial:transform’: [0.0014372978798746772, 0.0, -109.07797340998921, 0.0, -0.001437297879988364, 41.00370749308155]}, {‘asset’: ‘5’, ‘transform’: {‘scale’: [32.0, 32.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘4’, ‘spatial:shape’: [1400, 2448], ‘spatial:transform’: [0.0028745957597493543, 0.0, -109.07797340998921, 0.0, -0.002874595759976728, 41.00370749308155]}, {‘asset’: ‘6’, ‘transform’: {‘scale’: [64.0, 64.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘5’, ‘spatial:shape’: [700, 1224], ‘spatial:transform’: [0.005749191519498709, 0.0, -109.07797340998921, 0.0, -0.005749191519953456, 41.00370749308155]}, {‘asset’: ‘7’, ‘transform’: {‘scale’: [128.0, 128.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘6’, ‘spatial:shape’: [350, 612], ‘spatial:transform’: [0.011498383038997417, 0.0, -109.07797340998921, 0.0, -0.011498383039906912, 41.00370749308155]}, {‘asset’: ‘8’, ‘transform’: {‘scale’: [256.0, 256.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘7’, ‘spatial:shape’: [175, 306], ‘spatial:transform’: [0.022996766077994835, 0.0, -109.07797340998921, 0.0, -0.022996766079813824, 41.00370749308155]}, {‘asset’: ‘9’, ‘transform’: {‘scale’: [512.0, 512.0], ‘translation’: [0.0, 0.0]}, ‘derived_from’: ‘8’, ‘spatial:shape’: [88, 153], ‘spatial:transform’: [0.04599353215598967, 0.0, -109.07797340998921, 0.0, -0.04599353215962765, 41.00370749308155]}], ‘resampling_method’: ‘average’}proj:code :EPSG:4326proj:wkt2 :GEOGCRS[“WGS 84”,ENSEMBLE[“World Geodetic System 1984 ensemble”,MEMBER[“World Geodetic System 1984 (Transit)”],MEMBER[“World Geodetic System 1984 (G730)”],MEMBER[“World Geodetic System 1984 (G873)”],MEMBER[“World Geodetic System 1984 (G1150)”],MEMBER[“World Geodetic System 1984 (G1674)”],MEMBER[“World Geodetic System 1984 (G1762)”],MEMBER[“World Geodetic System 1984 (G2139)”],MEMBER[“World Geodetic System 1984 (G2296)”],ELLIPSOID[“WGS 84”,6378137,298.257223563,LENGTHUNIT[“metre”,1]],ENSEMBLEACCURACY[2.0]],PRIMEM[“Greenwich”,0,ANGLEUNIT[“degree”,0.0174532925199433]],CS[ellipsoidal,2],AXIS[“geodetic latitude (Lat)”,north,ORDER[1],ANGLEUNIT[“degree”,0.0174532925199433]],AXIS[“geodetic longitude (Lon)”,east,ORDER[2],ANGLEUNIT[“degree”,0.0174532925199433]],USAGE[SCOPE[“Horizontal component of 3D system.”],AREA[“World.”],BBOX[-90,-180,90,180]],ID[“EPSG”,4326]]spatial:dimensions :[‘y’, ‘x’]spatial:registration :pixelspatial:transform_type :affinespatial:shape :[44800, 78336]spatial:transform :[8.983111749216732e-05, 0.0, -109.07797340998921, 0.0, -8.983111749927275e-05, 41.00370749308155]spatial:bbox :[-109.07797340998921, 36.97927342911413, -102.0409629901228, 41.00370749308155] Using the multiscales convention and building on carbonplan’s zarr-layer, we can visualize the nine-year mosaic. If the viewer below is not working, try fullscreen mode here. Examples of Changes in Boulder, Colorado The video below shows the normalized change scores around Boulder, Colorado. It is a quick qualitative pass rather than a formal validation exercise, but several patterns stand out more clearly once the scores are viewed over time. Takeaways and Next Steps This follow-up suggests that leave-one-out medoid scoring, combined with robust time-wise normalization, is more useful for inspecting AEF change signal than raw distance alone. The normalization makes change magnitude more comparable across pixels, while the leave-one-out medoid highlights anomalous signal across the full observation window rather than only between adjacent time steps. This does not make AEF embeddings a complete change-detection product, but it does provide a clearer and more consistent way to identify which years look unusual at each pixel. That matters because AEF operates at a spatial and temporal resolution where many changes are subtle, cumulative, or mixed with recurring seasonal variation. A normalized score does not solve interpretation by itself, but it makes the first-pass search for interesting change much more practical. In this notebook, we used run_mosaics_change on the RasterFlow Remote client to compute change scores from the global AEF mosaic, built a multiscales derivative for browser-based exploration, and reviewed several examples around Boulder. Followup work may include using the embeddings to also help filter the object types of interest for taking yet another step closer towards a more complete change-detection product, especially where we have external labels or stronger domain priors. If you are interested in this approach, reach out to the Wherobots team at support@wherobots.com. For a complementary approach aggregating embeddings at the field level, see Zonal Statistics on AlphaEarth Embeddings. Get Started with RasterFlow START BUILDING Key takeawaysRaw AlphaEarth Foundations embedding distances do not live on a consistent scale across pixels or land-cover types. This follow-up scores each annual embedding by how much it stands out from the rest of its local time series using leave-one-out (LOO) medoid scores in embedding space, then robust z-score scaling through time per pixel.The workflow reads the global AEF Zarr mosaic on Source Cooperative: annual 64-band embeddings at 10-meter resolution in EPSG:4326, nine snapshots from 2017 through 2025. The global store is 3.81 PiB with about 1.02 billion chunks; the demo subsets Colorado rather than the full mosaic, and only land-intersecting chunks are present.RasterFlow's run_mosaics_change computed both difference-over-time and LOO medoid scores with AlphaEarth cosine distance and robust_time_zscore=True. The Colorado result is a ~235 GB Zarr (9 time steps × 2 score bands). build_zarr_multiscales produced a public multiscale mosaic at s3://wherobots-examples/rasterflow/mosaics/co-aef-change.zarr.The post is a practical workflow, not a definitive change-detection benchmark. LOO medoid plus robust time-wise normalization makes change magnitude more comparable across pixels and highlights anomalous years across the full nine-year window, not only between adjacent years. Examples around Boulder are qualitative.
Detecting Objects From Text Prompts with RasterFlow and Segment Anything 3 Posted on May 4, 2026October 3, 2026 by Ryan Avery RasterFlow now Supports Detections from Text! RasterFlow now makes it simple to run promptable geospatial vision models across large aerial and satellite imagery collections, removing the need to build bespoke inference pipelines. With our new SAM 3 support, you can prompt for concepts like “roofs”, “roads”, or “shipping containers” and turn those detections into vector outputs ready for analysis in Wherobots, from city scale to country scale. Here we see SAM 3 localize and capture roofs really well. It also segments roads pretty cleanly, though there is still room for improvement. Results are shown on the inference input, a NAIP 30 centimeter basemap, and include all instance pixels above 50% confidence. In this post, we put SAM 3 to the test detecting roofs in suburban neighborhoods, shipping containers in crowded loading docks, and tractors across agricultural landscapes. Throughout, we comment on where it succeeds, where it falls short, and how results compare to previous SAM models. Finally, we discuss what’s next for computer vision in Earth observation (EO) and how the community can build on models like SAM to create more promptable, flexible applications that handle the scale and diversity of remote sensing imagery. How Segment Anything Model 3 Improves on SAM 1 and SAM 2 for Remote Sensing In 2023, the Segment Anything Model (SAM) set a new paradigm for computer vision. While most models were trained to address one task at a time, SAM demonstrated that a single model could reliably classify, localize, and segment many kinds of objects in complex natural scenes. SAM set a new standard task for computer vision models that went beyond classification or semantic segmentation: Promptable Visual Segmentation (PVS), which takes spatial prompts such as boxes, points, or masks as input and predicts masks. However, SAM 1’s performance on out-of-distribution imagery domains had issues. Back in 2023, while I was working on detection projects with 10 meter resolution imagery, I found it often took multiple rounds of prompting to get useful segments, and many results still needed manual cleanup. For example, in a rural town, SAM 1 can segment many features across different spatial scales, but it still misses all roads. SAM 1 segments many features across different spatial scales in Dikwa, Nigeria, but still misses roads. SAM 2 improved accuracy and added video support, but both SAM 1 and SAM 2 were limited to making predictions without associated labels. The masks they produced were not grounded in categories useful for deriving insights. Fast forward to today: SAM 3 addresses Promptable Concept Segmentation (PCS), a task where a model can accept either spatial prompts (masks, boxes, pixels) or text prompts like “cat”, “dog”, even “roofs”! The SAM 3 architecture supports Promptable Concept Segmentation, accepting text and image exemplars in addition to spatial prompts to produce masks with labeled concepts. It also benefits from pretraining on overhead imagery. Source: YouTube. This opens up new possibilities for detecting objects in imagery. Because SAM 3’s training data spans many imaging domains, including overhead aerial imagery, it can succeed in many Earth observation contexts where previous model generations struggled. For example, one can use SAM 3 to predict “roofs” in NAIP imagery simply by asking, with no model training or ad hoc labeling needed, and get pretty stellar results. With RasterFlow, we can create imagery mosaics and predict roofs simply by prompting SAM 3 with short noun phrases like “roofs”. To put SAM 3 to the test on Earth observation imagery, we generated many more SAM 3 predictions on top of National Agriculture Program 30 centimeter imagery using RasterFlow, our scalable mosaic building and inference engine. Below details the performance of RasterFlow on this high resolution detection task. RasterFlow TaskBilled RuntimeOutputTotal Output SizeSpatial Scale of Outputbuild_gti_mosaic11m 47sNAIP Zarr mosaic133.08 GB (123.94 GiB)~114.15 km × 66.81 kmpredict_mosaic_geometries39m 51sGeoParquet geometry results263.85 MB (251.63 MiB)~111.02 km × 66.81 km See an AI agent take plain-language question and orchestrating pipelines to end results using RasterFlow and the Wherobots MCP server, check out the upcoming live session. See it in action Join us live How to Run SAM 3 on Aerial and Satellite Imagery with RasterFlow What used to take a complex mix of imagery ETL, bespoke inference pipelines, and self-provisioned infrastructure for large Earth observation processing now takes under an hour and two simple Python functions. Let’s check it out below. Build an Aerial Imagery Mosaic from NAIP First, we will generate a mosaic: a seamless, stitched-together image from many independent remote sensing scenes. RasterFlow handles all the data sourcing, loading, cleaning, and partitioning into a data asset optimized for inference for a particular model. from rasterflow_remote import RasterflowClient rf_client = RasterflowClient() mosaic_output = rf_client.build_gti_mosaic( gti="s3://wherobots-examples/rasterflow/indexes/naip_index.parquet", aoi="s3://wherobots-examples/rasterflow/aois/marion_county.parquet", bands=["red", "green", "blue", "nir"], location_field="url", crs_epsg=3857, time_column="year", skip_xy_coords=False, xy_chunksize=1024, query="res == 0.3 and time >= '2022-01-01' and time <= '2023-01-01'", requester_pays=True, sort_field="time", ) print(mosaic_output.uri) Run the SAM 3 Inference on the Mosaic With our mosaic built, we can now run inference with SAM 3. Unlike more rigid models which only predict one category, SAM 3 can accept one or more text prompts and detect all matching objects in a single pass. The runtime for a given batch of imagery scales linearly with the number of detections. More prompts tends to mean more detections, so keep in mind that the more prompts you add, the longer you can expect an inference run to take. from rasterflow_remote.data_models import GeometryActorEnum, MergeModeEnum model_output = rf_client.predict_mosaic_geometries( store="s3://wherobots-examples/rasterflow/mosaics/marion_county.zarr", model_path="https://huggingface.co/wherobots/sam3-text-geometry-pt2/resolve/main/full_sam3_pipeline.pt2", patch_size=1008, clip_size=0, device="cuda", features=["red", "green", "blue"], labels=["roads", "airplanes", "airports", "roofs", "solar panels", "swimming pools", "shipping containers", "tractors"], actor=GeometryActorEnum.TEXT_TO_VECTOR_GEOMETRIES, max_batch_size=1, confidence_threshold=0.5, merge_mode=MergeModeEnum.NONE, xy_block_multiplier=1, ) Analyze the SAM 3 Detections with WherobotsDB We can visualize both the mosaic and detections directly in the notebook with an embedded RasterFlow map. The RGB imagery mosaic and the SAM 3 detections have been web-optimized with RasterFlow for fast and fluid browsing. If the embed does not load in your environment, open it in a new tab here. After visually exploring detections, we can load the results into WherobotsDB for quantitative analysis. WherobotsDB lets us post-process geometries, calculate zonal statistics on other rasters, count objects, measure clustering, and much more. import os from sedona.spark import * from pyspark.sql.functions import * from wherobots import vtiles config = ( SedonaContext.builder() .getOrCreate() ) sedona = SedonaContext.create(config) parquet_path = "s3://wherobots-examples/rasterflow/model-outputs/marion_county_sam3/" df = sedona.read.format("geoparquet").load(parquet_path) df.printSchema() df.show(10) Here we’ll use ST_Area to estimate the total square km area of all roofs in Marion County. roofs_df = df.filter(col("label") == "roofs") source_srid = roofs_df.selectExpr("ST_SRID(geometry) AS srid").first()["srid"] source_crs = f"EPSG:{source_srid}" print(source_crs) # Run the SRID inspection cell above first so source_crs reflects the data's actual CRS. roof_areas = roofs_df.withColumn( "area_sq_m", expr(f"ST_AreaSpheroid(ST_Transform(geometry, '{source_crs}', 'EPSG:4326'))"), ).cache() roof_areas_agg = roof_areas.agg( sum("area_sq_m").alias("total_area_sq_m"), (sum("area_sq_m") / lit(1_000_000.0)).alias("total_area_sq_km"), ) roof_areas_agg.show(truncate=False) roof_size_stats = roof_areas.agg( count("*").alias("roof_count"), avg("area_sq_m").alias("avg_roof_sq_m"), expr("percentile_approx(area_sq_m, 0.5)").alias("median_roof_sq_m"), (avg("area_sq_m") * lit(10.7639)).alias("avg_roof_sq_ft"), ) stats = roof_size_stats.first() print(f"roof_count: {stats['roof_count']:,}") print(f"avg_roof_sq_m: {stats['avg_roof_sq_m']:.2f}") print(f"median_roof_sq_m: {stats['median_roof_sq_m']:.2f}") label_counts = df.groupBy("label").count().orderBy(col("count").desc(), col("label")) label_counts.show(truncate=False) We can also generate web-optimized vectors in PMTiles format. PMTiles are easily shareable and plug directly into geospatial visualization applications, making them a great choice for distributing detection results. df_tiles = df.withColumn("layer", col("label")) output_path = 's3://wherobots-examples/rasterflow/model-outputs/marion_county_sam3.pmtiles' vtiles.generate_pmtiles(df_tiles, output_path) SAM 3 Accuracy on Earth Observation: Where It Succeeds and Where It Falls Short After experimenting with SAM 3 on NAIP, I’m impressed with the range of categories that SAM 3 can positively identify. At the same time, the model still has clear precision and recall gaps for more niche semantic categories, like “tractors” or “shipping containers”. SAM 3 correctly detects tractors in a harvested field. In an agricultural context, SAM 3 finds some tractors but also misses some. SAM 3 correctly identifies shipping containers in a storage yard. False positives: SAM 3 labels rectangular roofs as “shipping containers”. These examples illustrate the gap between SAM 3’s strengths on common categories and its current limitations on more niche ones. I’m bullish that if SAM 3 were fine-tuned on a larger corpus of mixed high-resolution imagery and high-quality labels, it would perform even better outside of its primary domain of natural imagery. Even so, I think the potential for SAM 3 for simpler categories like “roofs” is underutilized. SAM 3 could potentially improve many existing datasets we rely on to make decisions, like Overture Buildings and detections of other kinds of structures. What to Try Next with SAM 3 on Wherobots Some things we didn’t showcase but I recommend trying in your experiments: Can you find and count vehicles that are a certain color? Can you use SAM 3 to segment city streets? What about rural roads? Try to map forest canopy! SAM 3 can do a pretty great job at segmenting concepts that don’t map cleanly to individual objects, but are more textural. For a larger-scale example of RasterFlow in production, see how RasterFlow processed 2.3B Overture Maps features for Fields of the World. If you’re excited to try SAM 3 on Wherobots, sign up for RasterFlow Private Preview. You can also get in touch at ryan@wherobots.com or talk to us here. I’d love to hear about what you’re looking to build and how SAM 3 could fit into your detection workflows. Try SAM3 on Wherobots START BUILDING Key takeawaysRasterFlow now supports SAM 3 promptable concept segmentation: text prompts such as ‘roofs’, ‘roads’, or ‘shipping containers’ produce vector outputs from aerial and satellite mosaics. SAM 3 accepts spatial prompts or text, and it was pretrained on overhead imagery, unlike SAM 1/2.On Marion County NAIP 30 cm, build_gti_mosaic billed 11 minutes 47 seconds and wrote a 133.08 GB Zarr mosaic (~114.15 km × 66.81 km). predict_mosaic_geometries billed 39 minutes 51 seconds and wrote 263.85 MB of GeoParquet. Eight labels were prompted in one pass at confidence_threshold 0.5.SAM 3 localizes roofs well on the NAIP basemap and segments roads reasonably, with room for improvement. Niche classes such as tractors and shipping containers show clear precision and recall gaps, including rectangular roofs labeled as shipping containers.What used to take a mix of imagery ETL, bespoke inference pipelines, and self-provisioned infrastructure now takes under an hour and two Python functions: build_gti_mosaic then predict_mosaic_geometries. Detections load into WherobotsDB as GeoParquet for ST_AreaSpheroid, counts, and PMTiles via vtiles.generate_pmtiles.
How We Delivered “Fields of The World” with RasterFlow: A Planetary-Scale GeoAI Pipeline Posted on April 23, 2026October 4, 2026 by Ben Pruden Author: Len Strnad Fields of the World (FTW) is a Taylor Geospatial effort to produce globally consistent agricultural field-boundary data for land-use monitoring, food-system analysis, and model development. For the 2024-2025 global release, Taylor Geospatial partnered with Wherobots to run their latest FTW model, PRUE, on Wherobots RasterFlow; see the PRUE paper. In this notebook, we walk through the production pipeline behind that release. The PRUE model turns seasonal Sentinel-2 composites into global field and field_boundaries predictions, and RasterFlow handles the larger systems problem: running data access, compositing, inference, and vectorization as one coordinated workflow and publishing the results as analysis-ready raster and vector products. The focus here is practical architecture rather than model internals. We cover how the pipeline is structured, what it produced at global scale, and the implementation choices that made the run reproducible. What we cover: RasterFlow and why it is designed for reproducible, scalable global runs. A Scale Profile with artifact sizes/object counts for this dataset. The RasterFlow workflow stages and resulting outputs: build_mosaic predict_mosaic vectorize_mosaic. Implementation notes in Appendix — Technical Notes. RasterFlow Execution Model for a Global-Scale GeoAI Pipeline This pipeline runs on RasterFlow, Wherobots’ planetary-scale inference engine for Earth Intelligence. RasterFlow provides a reproducible way to run a global-scale GeoAI pipeline. It is a geospatial workflow system backed by Flyte V2 and Ray. We chose Flyte because long-running global jobs need strong workflow semantics: versioned executions, retries, lineage, and reproducible reruns. We chose Ray Datasets because raster inference and vectorization are naturally blockwise workloads, and Ray gives us elastic parallelism and task streaming support to support the many small task problem. The rasterflow_remote client exposes common GeoAI workflow steps while running in each organization’s isolated namespace. As a result, resource controls, failure containment, and reruns stay scoped to that organization. We construct the client and load the model config for the PRUE model: from rasterflow_remote.model_registry import get_model_registry_config, ModelRegistryEnum from rasterflow_remote import RasterflowClient client = RasterflowClient() # see https://huggingface.co/wherobots/prue-pt2 for model details cfg = get_model_registry_config(ModelRegistryEnum.HUGGINGFACE, "wherobots/prue-pt2") Build Mosaic build_mosaic converts raw satellite scenes into analysis-ready feature mosaics on a shared grid. For the FTW production run, this stage computes planting and harvest composites, writes feature COGs, and then materializes a global feature mosaic that can be stacked with model outputs. We persist that global feature product in Zarr because chunked, cloud-native storage lets us read small spatial windows directly from object storage during validation, inference, and downstream analytics. from rasterflow_remote import DatasetEnum from datetime import datetime mosaic = client.build_mosaic( datasets=[DatasetEnum.S2_MED_PLANTING, DatasetEnum.S2_MED_HARVEST], aoi="https://github.com/fieldsoftheworld/ftw-inference-app/blob/main/src/data/s2-grid.json", start=datetime(2023, 1, 1), end=datetime(2025, 1, 1), crs_epsg=4326, ) For implementation details, see Appendix: Feature COG and Appendix: GDAL notes. Inspect the published feature mosaic The produced feature mosaic is published as a Zarr store on Source Cooperative. Therefore, we can inspect structure, bands, and a spatial subset directly. import xarray as xr import rasterix mosaic_ds = xr.open_zarr( "https://data.source.coop/ftw/global-data/features/zarr/alpha/global.zarr" ).pipe(rasterix.assign_index) mosaic_ds <xarray.Dataset> Size: 502TB Dimensions: (time: 2, band: 10, y: 1566049, x: 4007517) Coordinates: * time (time) datetime64[ns] 16B 2024-01-01 2025-01-01 * band (band) object 80B 's2med_harvest:B02' ... 's2med_planting:N_... * y (y) float64 13MB 83.75 83.75 83.75 ... -56.93 -56.93 -56.93 * x (x) float64 32MB -180.0 -180.0 -180.0 ... 180.0 180.0 180.0 spatial_ref int64 8B ... Data variables: variables (time, band, y, x) float32 502TB dask.array<chunksize=(1, 1, 4096, 4096), meta=np.ndarray> Indexes: ┌ x RasterIndex (crs=None) └ yxarray.DatasetDimensions:time: 2band: 10y: 1566049x: 4007517Coordinates: (5)time(time)datetime64[ns]2024-01-01 2025-01-01array(['2024-01-01T00:00:00.000000000', '2025-01-01T00:00:00.000000000'], dtype='datetime64[ns]')band(band)object‘s2med_harvest:B02’ … ‘s2med_p…array(['s2med_harvest:B02', 's2med_harvest:B03', 's2med_harvest:B04', 's2med_harvest:B08', 's2med_harvest:N_VALID_PIXELS', 's2med_planting:B02', 's2med_planting:B03', 's2med_planting:B04', 's2med_planting:B08', 's2med_planting:N_VALID_PIXELS'], dtype=object)y(y)float6483.75 83.75 83.75 … -56.93 -56.93[1566049 values with dtype=float64]x(x)float64-180.0 -180.0 … 180.0 180.0[4007517 values with dtype=float64]spatial_ref()int64…crs_wkt :GEOGCS[“WGS 84”,DATUM[“WGS_1984”,SPHEROID[“WGS 84”,6378137,298.257223563,AUTHORITY[“EPSG”,”7030″]],AUTHORITY[“EPSG”,”6326″]],PRIMEM[“Greenwich”,0,AUTHORITY[“EPSG”,”8901″]],UNIT[“degree”,0.0174532925199433,AUTHORITY[“EPSG”,”9122″]],AXIS[“Latitude”,NORTH],AXIS[“Longitude”,EAST],AUTHORITY[“EPSG”,”4326″]]semi_major_axis :6378137.0semi_minor_axis :6356752.314245179inverse_flattening :298.257223563reference_ellipsoid_name :WGS 84longitude_of_prime_meridian :0.0prime_meridian_name :Greenwichgeographic_crs_name :WGS 84horizontal_datum_name :World Geodetic System 1984grid_mapping_name :latitude_longitudespatial_ref :GEOGCS[“WGS 84”,DATUM[“WGS_1984”,SPHEROID[“WGS 84”,6378137,298.257223563,AUTHORITY[“EPSG”,”7030″]],AUTHORITY[“EPSG”,”6326″]],PRIMEM[“Greenwich”,0,AUTHORITY[“EPSG”,”8901″]],UNIT[“degree”,0.0174532925199433,AUTHORITY[“EPSG”,”9122″]],AXIS[“Latitude”,NORTH],AXIS[“Longitude”,EAST],AUTHORITY[“EPSG”,”4326″]][1 values with dtype=int64]Data variables: (1)variables(time, band, y, x)float32dask.array<chunksize=(1, 1, 4096, 4096), meta=np.ndarray> Array Chunk Bytes 456.64 TiB 64.00 MiB Shape (2, 10, 1566049, 4007517) (1, 1, 4096, 4096) Dask graph 7499140 chunks in 2 graph layers Data type float32 numpy.ndarray 2 1 4007517 1566049 10 Indexes: (1)xyRasterIndex (crs=None)RasterIndex(crs=None) AxisAffineTransformIndex(AxisAffineTransform(a=8.983e-05, b=0, c=-180, d=0, e=-8.983e-05, f=83.75, axis=X, dim='x')) AxisAffineTransformIndex(AxisAffineTransform(a=8.983e-05, b=0, c=-180, d=0, e=-8.983e-05, f=83.75, axis=Y, dim='y')) Persisting an intermediate global Zarr mosaic lets us query any region without reprojecting or resampling. Training data for the PRUE model is also aligned to EPSG:4326, so feature and prediction stores remain grid-compatible for side-by-side analysis. For data layout details, see Appendix: Zarr internals. For the examples below, we use the same Japan AOI shown later in the PMTiles preview so the raster and vector artifacts are easy to compare. For more information on xarray, see Appendix: Xarray notes. import matplotlib.pyplot as plt import numpy as np bbox = { "x": slice(140.24, 140.34), "y": slice(37.36, 37.26), } subset = mosaic_ds.sel(x=bbox["x"], y=bbox["y"]) geo_aspect = 1 / np.cos(np.deg2rad(float(subset.y.mean()))) fig, axes = plt.subplots(1, 2, figsize=(9, 9), constrained_layout=True) for ax, band_prefix, title in [ (axes[0], "s2med_planting", "Planting RGB (PMTiles AOI, 2025)"), (axes[1], "s2med_harvest", "Harvest RGB (PMTiles AOI, 2025)"), ]: subset["variables"].sel( time="2025-01-01", band=[f"{band_prefix}:B04", f"{band_prefix}:B03", f"{band_prefix}:B02"], ).plot.imshow(ax=ax, robust=True) ax.set_aspect(geo_aspect) ax.set_title(title) plt.show() See additional information in Appendix: Zarr internals and Appendix: Xarray notes. Predict Mosaic predict_mosaic runs global model inference over the feature mosaic and writes one prediction Zarr store. You provide the model config, and the workflow executes inference across the global grid. Keeping the result in Zarr preserves the same chunked access pattern as the feature store, so validation and downstream vectorization can stay blockwise and cloud-native instead of stitching regional outputs back together later. predictions = client.predict_mosaic( store="https://data.source.coop/ftw/global-data/features/zarr/alpha/global.zarr", model_path=cfg.model_path, patch_size=cfg.patch_size, clip_size=cfg.clip_size, device=cfg.device, features=cfg.features, labels=cfg.labels, actor=cfg.actor, max_batch_size=cfg.max_batch_size, merge_mode=cfg.merge_mode, ) For internals and scheduling details, see Appendix: config, Appendix: Ray usage, and Appendix: distributed inference. For a similar inference step using text-prompted detection instead of segmentation models, see how Segment Anything 3 runs on aerial imagery with RasterFlow. Inspect the published prediction mosaic The prediction Zarr is also published on Source Cooperative. Let’s verify shape, bands, and value ranges. The shape, bounds, and time remain aligned with the feature mosaic; however, the bands now represent predicted classes. pred_ds = xr.open_zarr( "https://data.source.coop/ftw/global-data/predictions/zarr/alpha/global.zarr" ).pipe(rasterix.assign_index) pred_ds <xarray.Dataset> Size: 151TB Dimensions: (time: 2, band: 3, y: 1566049, x: 4007517) Coordinates: * time (time) datetime64[ns] 16B 2024-01-01 2025-01-01 * band (band) <U20 240B 'non_field_background' ... 'field_boundaries' * y (y) float64 13MB 83.75 83.75 83.75 ... -56.93 -56.93 -56.93 * x (x) float64 32MB -180.0 -180.0 -180.0 ... 180.0 180.0 180.0 spatial_ref int64 8B ... Data variables: variables (time, band, y, x) float32 151TB dask.array<chunksize=(1, 1, 2048, 2048), meta=np.ndarray> Indexes: ┌ x RasterIndex (crs=None) └ yxarray.DatasetDimensions:time: 2band: 3y: 1566049x: 4007517Coordinates: (5)time(time)datetime64[ns]2024-01-01 2025-01-01array(['2024-01-01T00:00:00.000000000', '2025-01-01T00:00:00.000000000'], dtype='datetime64[ns]')band(band)<U20‘non_field_background’ … ‘fiel…array(['non_field_background', 'field', 'field_boundaries'], dtype='<U20')y(y)float6483.75 83.75 83.75 … -56.93 -56.93[1566049 values with dtype=float64]x(x)float64-180.0 -180.0 … 180.0 180.0[4007517 values with dtype=float64]spatial_ref()int64…crs_wkt :GEOGCS[“WGS 84”,DATUM[“WGS_1984”,SPHEROID[“WGS 84”,6378137,298.257223563,AUTHORITY[“EPSG”,”7030″]],AUTHORITY[“EPSG”,”6326″]],PRIMEM[“Greenwich”,0,AUTHORITY[“EPSG”,”8901″]],UNIT[“degree”,0.0174532925199433,AUTHORITY[“EPSG”,”9122″]],AXIS[“Latitude”,NORTH],AXIS[“Longitude”,EAST],AUTHORITY[“EPSG”,”4326″]]semi_major_axis :6378137.0semi_minor_axis :6356752.314245179inverse_flattening :298.257223563reference_ellipsoid_name :WGS 84longitude_of_prime_meridian :0.0prime_meridian_name :Greenwichgeographic_crs_name :WGS 84horizontal_datum_name :World Geodetic System 1984grid_mapping_name :latitude_longitudespatial_ref :GEOGCS[“WGS 84”,DATUM[“WGS_1984”,SPHEROID[“WGS 84”,6378137,298.257223563,AUTHORITY[“EPSG”,”7030″]],AUTHORITY[“EPSG”,”6326″]],PRIMEM[“Greenwich”,0,AUTHORITY[“EPSG”,”8901″]],UNIT[“degree”,0.0174532925199433,AUTHORITY[“EPSG”,”9122″]],AXIS[“Latitude”,NORTH],AXIS[“Longitude”,EAST],AUTHORITY[“EPSG”,”4326″]][1 values with dtype=int64]Data variables: (1)variables(time, band, y, x)float32dask.array<chunksize=(1, 1, 2048, 2048), meta=np.ndarray> Array Chunk Bytes 136.99 TiB 16.00 MiB Shape (2, 3, 1566049, 4007517) (1, 1, 2048, 2048) Dask graph 8982630 chunks in 2 graph layers Data type float32 numpy.ndarray 2 1 4007517 1566049 3 Indexes: (1)xyRasterIndex (crs=None)RasterIndex(crs=None) AxisAffineTransformIndex(AxisAffineTransform(a=8.983e-05, b=0, c=-180, d=0, e=-8.983e-05, f=83.75, axis=X, dim='x')) AxisAffineTransformIndex(AxisAffineTransform(a=8.983e-05, b=0, c=-180, d=0, e=-8.983e-05, f=83.75, axis=Y, dim='y')) Let’s plot the three labels for the same PMTiles-area AOI so the raster predictions line up with the vector preview later in the post: import numpy as np bbox = { "x": slice(140.24, 140.34), "y": slice(37.36, 37.26), } pred_subset = pred_ds.sel(x=bbox["x"], y=bbox["y"]) geo_aspect = 1 / np.cos(np.deg2rad(float(pred_subset.y.mean()))) grid = pred_subset["variables"].plot.imshow(col="band", row="time", figsize=(12, 8)) for ax in grid.axes.flat: ax.set_aspect(geo_aspect) plt.show() Zarr metadata for those curious is available in Appendix: Zarr internals. Vectorize Mosaic vectorize_mosaic converts prediction rasters into field-boundary GeoParquet outputs. You specify feature bands, threshold, and method. Then the workflow writes distributed vector outputs for analytics and downstream publishing. We write GeoParquet because it keeps the result columnar, splittable, and easy to consume from batch analytics systems. Conceptually, this output is a distributed geospatial table rather than an array: each row is a polygonized field candidate, the geometry column holds the boundary, and the remaining columns carry the scalar attributes you want to inspect in GeoPandas or query later in SedonaDB and WherobotsDB. For distributed implementation details, see Appendix: vectorization. from rasterflow_remote.data_models import VectorizeMethodEnum, SemSegRasterioConfig vectors = client.vectorize_mosaic( store="https://data.source.coop/ftw/global-data/predictions/zarr/alpha/global.zarr", features=["field"], threshold=0.5, vectorize_method=VectorizeMethodEnum.SEMANTIC_SEGMENTATION_RASTERIO, vectorize_config=SemSegRasterioConfig(stats=False, medial_skeletonize=False), ) A lightweight way to inspect that schema locally is: import geopandas as gpd vector_preview = gpd.read_parquet(vectors.uri) vector_preview.head(3) That gives you a normal GeoDataFrame view of the vector output: one geometry column plus the per-row attributes emitted by the vectorization stage. Implementation details for distributed/blockwise vectorization are in Appendix: vectorization. PMTiles PMTiles packages vector outputs for fast, interactive map delivery. We use it here because a single archive is easy to publish, cache, and stream into browser maps without standing up a tile service. We also support generating PMTiles from large GeoParquet outputs directly in WherobotsDB using our scalable PMTiles generator workflow, as described here: How to Generate PMTiles for Overture Maps with WherobotsDB VTiles. Scale Profile This section summarizes the scale of each pipeline step and the size of the generated outputs. Understanding the scale of this production run will help when we discuss design choices later in this blog post. ArtifactPipeline StepS3 LocationSizeObject CountAdditional Scale SignalFeature COGsbuild_mosaics3://us-west-2.opendata.source.coop/tge-labs/ftw-global-data/features/cogs/153 TB90,918Each COG is a median composite from ~5-10 Sentinel-2 scenes per pixelFeature Zarr mosaicbuild_mosaics3://us-west-2.opendata.source.coop/tge-labs/ftw-global-data/features/zarr/alpha/global.zarr150 TB363,9997,499,140 logical chunks (sharded to keep object count manageable)Prediction Zarr mosaicpredict_mosaics3://us-west-2.opendata.source.coop/tge-labs/ftw-global-data/predictions/zarr/alpha/global.zarr45 TB84,8778,982,630 logical chunks (also sharded)Vector outputs (GeoParquet)vectorize_mosaics3://us-west-2.opendata.source.coop/tge-labs/ftw-global-data/predictions/vectors/alpha/results/675 GB1,0008,217,195,679 rows Aggregate data output (above artifacts): ~348.7 TB across 540,794 objects. RasterFlow summary You can reproduce this end-to-end global-scale GeoAI pipeline with three API calls: from datetime import datetime from rasterflow_remote import RasterflowClient, DatasetEnum from rasterflow_remote.model_registry import get_model_registry_config, ModelRegistryEnum from rasterflow_remote.data_models import VectorizeMethodEnum, SemSegRasterioConfig client = RasterflowClient() cfg = get_model_registry_config(ModelRegistryEnum.HUGGINGFACE, "wherobots/prue-pt2") mosaic = client.build_mosaic( datasets=[DatasetEnum.S2_MED_PLANTING, DatasetEnum.S2_MED_HARVEST], aoi="https://github.com/fieldsoftheworld/ftw-inference-app/blob/main/src/data/s2-grid.json", start=datetime(2023, 1, 1), end=datetime(2025, 1, 1), crs_epsg=4326, ) predictions = client.predict_mosaic( store=mosaic.uri, model_path=cfg.model_path, patch_size=cfg.patch_size, clip_size=cfg.clip_size, device=cfg.device, features=cfg.features, labels=cfg.labels, actor=cfg.actor, max_batch_size=cfg.max_batch_size, merge_mode=cfg.merge_mode, ) vectors = client.vectorize_mosaic( store=predictions.uri, features=["field"], threshold=0.5, vectorize_method=VectorizeMethodEnum.SEMANTIC_SEGMENTATION_RASTERIO, vectorize_config=SemSegRasterioConfig(stats=False, medial_skeletonize=False), ) In short, this global-scale GeoAI pipeline with RasterFlow starts from global feature mosaics, runs one global inference workflow, and publishes both analytics-ready and map-ready outputs. If you want a hands-on starting point, sign up for the RasterFlow Private Preview today and try the FTW notebook in the Solution Gallery. If your team is evaluating global-scale raster + vector production workflows, this is exactly what RasterFlow private preview is for. For API details and additional examples, see the Rasterflow documentation. Appendix — Technical Notes Building Feature COGs Internally, build_mosaic orchestrates four key steps: Seasonal DOY filtering — candidate scenes are filtered by day-of-year windows derived from latitude-aware seasonal heuristics. Quality masking — cloud/quality masks suppress invalid observations. Greedy scene selection — we iteratively pick scenes that maximize uncovered valid area so we reach broad coverage with a bounded scene count. Temporal compositing — selected scenes are collapsed into median composites for downstream model features. The current DOY heuristic and greedy objective are intentionally conservative and production-stable, but there is room for improvement (for example, region-specific phenology priors, dynamic cloud climatology, and cost-aware objective tuning). We also produce a COG index, which is used with GDAL’s GTI driver to build the underlying global mosaic. import geopandas as gpd import fsspec with fsspec.open("https://data.source.coop/ftw/global-data/features/cogs/alpha/index.parquet") as f: index = gpd.read_parquet(f) index.head(3) tile_id geometry time dataset url 0 01FBE POLYGON ((180 -50.59941, 178.76332 -50.5626, 1… 2024-01-01 00:00:00+00:00 s2med_planting s3://us-west-2.opendata.source.coop/tge-labs/f… 1 01FBE POLYGON ((180 -50.59941, 178.76332 -50.5626, 1… 2025-01-01 00:00:00+00:00 s2med_planting s3://us-west-2.opendata.source.coop/tge-labs/f… 2 01FBF POLYGON ((180 -49.70001, 178.84184 -49.66599, … 2024-01-01 00:00:00+00:00 s2med_planting s3://us-west-2.opendata.source.coop/tge-labs/f… Mosaicking notes (GTI + Ray) After feature COGs are written, we build a global virtual mosaic index and then materialize aligned arrays on the target grid. This relies on GDAL’s GTI driver as the index layer and mosaicking. Grid configuration for this run: CRS: EPSG:4326 Resolution: 8.983119e-5 degrees (~10 m at equator) Resampling: cubic The GTI-backed approach keeps COG indexing separate from downstream chunk/block execution and maps cleanly to distributed scheduling. Zarr internals Zarr is the storage layer that makes the global feature and prediction mosaics usable as products rather than just intermediate files. Two layout choices matter here: Logical chunks are the units we want readers and compute stages to address. Shards are the larger storage objects we actually write to object storage. We split them on purpose. Small logical chunks keep regional reads and retry boundaries precise, while larger shards prevent the store from exploding into billions of tiny objects. Larger shards also lower overall S3 PUT and LIST costs and reduce request-rate pressure. For the feature store, the logical chunk is (1, 1, 4096, 4096) and shards are written at (1, 1, 12288, 12288), so each object packs a 3 x 3 tile of logical chunks. For the prediction store, the logical chunk is (1, 1, 2048, 2048) and shards are written at (1, 3, 8192, 8192), so each object packs all three class bands across a 4 x 4 spatial tile of logical chunks. The tradeoff is deliberate: larger shards improve object-store efficiency and reduce listing overhead, but smaller logical chunks still give us better partial reads, safer retries, and more control over Ray execution geometry. The feature Zarr mosaic metadata is as follows: import zarr group = zarr.open("https://data.source.coop/ftw/global-data/features/zarr/alpha/global.zarr") group["variables"].metadata.__dict__ {'shape': (2, 10, 1566049, 4007517), 'data_type': Float32(endianness='little'), 'chunk_grid': RegularChunkGrid(chunk_shape=(1, 1, 12288, 12288)), 'chunk_key_encoding': DefaultChunkKeyEncoding(separator='/'), 'codecs': (ShardingCodec(chunk_shape=(1, 1, 4096, 4096), codecs=(BytesCodec(endian=<Endian.little: 'little'>), ZstdCodec(level=0, checksum=False)), index_codecs=(BytesCodec(endian=<Endian.little: 'little'>), Crc32cCodec()), index_location=<ShardingCodecIndexLocation.end: 'end'>),), 'dimension_names': ('time', 'band', 'y', 'x'), 'fill_value': np.float32(nan), 'attributes': {'coordinates': 'spatial_ref', '_FillValue': 'AAAAAAAA+H8='}, 'storage_transformers': (), 'extra_fields': {}} The prediction zarr mosaic metadata is as follows: group = zarr.open("https://data.source.coop/ftw/global-data/predictions/zarr/alpha/global.zarr") group["variables"].metadata.__dict__ {'shape': (2, 3, 1566049, 4007517), 'data_type': Float32(endianness='little'), 'chunk_grid': RegularChunkGrid(chunk_shape=(1, 3, 8192, 8192)), 'chunk_key_encoding': DefaultChunkKeyEncoding(separator='/'), 'codecs': (ShardingCodec(chunk_shape=(1, 1, 2048, 2048), codecs=(BytesCodec(endian=<Endian.little: 'little'>), ZstdCodec(level=0, checksum=False)), index_codecs=(BytesCodec(endian=<Endian.little: 'little'>), Crc32cCodec()), index_location=<ShardingCodecIndexLocation.end: 'end'>),), 'dimension_names': ('time', 'band', 'y', 'x'), 'fill_value': np.float32(nan), 'attributes': {'coordinates': 'spatial_ref', '_FillValue': 'AAAAAAAA+H8='}, 'storage_transformers': (), 'extra_fields': {}} Xarray notes We use rasterix to attach analytical coordinates lazily so opening the global feature store does not require eagerly reading large coordinate vectors every time. Why this matters: x length: 1,566,049 y length: 4,007,517 At float64 precision, coordinates alone are roughly (1,566,049 + 4,007,517) * 8 ~= 45 MB before reading any model features. Operationally, our xarray pattern is: Open lazily from object storage. Slice first (.sel) to bound I/O. Materialize only when plotting/validating. This keeps exploratory analysis responsive while preserving direct comparability between feature and prediction stores on the same grid. The Inference Config The InferenceConfig is the contract between model packaging and distributed execution. It tells RasterFlow how to read features, how large each inference window should be, what runtime to launch, and how overlapping predictions should be merged back into the output mosaic. In practice, the fields fall into a few groups: model_path and actor select the model artifact and the inference runtime implementation. patch_size and clip_size define the read window and how much border area is discarded to suppress edge artifacts. features and labels define the exact tensor interface between the Zarr store and the model. device and max_batch_size control throughput versus memory pressure on the target worker. merge_mode defines how overlapping patch predictions are blended into the final raster. from rasterflow_remote.data_models import MergeModeEnum, InferenceActorEnum { # path to the pt2 archive on hugging face 'model_path': 'https://huggingface.co/wherobots/prue-pt2/resolve/main/prue-efnetb7.pt2', # an enum used to specify how to handle the model for inference 'actor': InferenceActorEnum.SEMANTIC_SEGMENTATION_PYTORCH, # patch size determines the size of the inputs provided to the model 'patch_size': 256, # clip size determines the size we clip off the borders to reduce edge effects during inference 'clip_size': 32, # device to run inference on, e.g. 'cuda' or 'cpu' 'device': 'cuda', # list of features in order as required by the model. These names map to the input Zarr band labels. 'features': ['s2med_harvest:B04', 's2med_harvest:B03', 's2med_harvest:B02', 's2med_harvest:B08', 's2med_planting:B04', 's2med_planting:B03', 's2med_planting:B02', 's2med_planting:B08'], # the named labels for the predicted classes. These will be the output band labels in the output Zarr. 'labels': ['non_field_background', 'field', 'field_boundaries'], # the maximum batch size assuming our default A10 GPU with 24GB of memory. 'max_batch_size': 128, # the method to use when merging overlapping predictions to reduce edge effects at the patch level 'merge_mode': MergeModeEnum.WEIGHTED_AVERAGE } How Ray is used for inference We chose Ray as the data-plane engine because the workload is embarrassingly parallel at block level, but still needs data-aware scheduling and actor pools for GPU inference. Ray is used as a bounded block-processing engine over sharded Zarr storage. Key mapping model: Logical chunks define storage math and indexability. Shards reduce object count by packing multiple chunks per object. Ray blocks define execution granularity and may span multiple chunks. This decoupling lets us tune compute batch shape without rewriting storage layout. In the data pipeline, Ray Data uses Arrow-backed transport so tabular metadata/state can move across stages with minimal copying. Practical takeaway: throughput scales with actor parallelism while per-task memory remains tied to block geometry, not global mosaic size. Distributed inference (wherobots-rasterflow) The InferenceConfig above feeds directly into block planning, actor setup, and overlap-aware merging. Internally, predict_mosaic orchestrates three phases: Block planning — derive execution blocks from chunk metadata and resource limits. Read/Infer/Write pipeline — execute overlap-aware reads, actor inference, and deterministic writes. Retry-safe commit — persist block outputs with stable target regions. # conceptual : chunk/shard metadata -> Ray blocks chunk_meta = read_zarr_chunk_metadata(store) blocks = plan_ray_blocks(chunk_meta, target_rows_per_block) ray_ds = ray.data.from_items(blocks) result = ( ray_ds .map(read_zarr_block_with_overlap) .map(run_inference_actor_pool) .map(clip_and_write_block) ) Our distributed approach to vectorizing vectorize_mosaic operates at block level so geometry generation scales linearly with partition count rather than requiring one global polygonization pass. High-level flow: Read prediction blocks from Zarr. Apply thresholding per block. Polygonize per block. Emit GeoParquet partitions and merge metadata. This keeps memory bounded and allows retries/reprocessing for individual partitions. Sign up for RasterFlow Private Preview Get Started Key takeawaysTaylor Geospatial partnered with Wherobots to run the PRUE model on RasterFlow for the 2024–2025 Fields of the World global release. PRUE turns seasonal Sentinel-2 composites into field and field_boundaries predictions; RasterFlow runs data access, compositing, inference, and vectorization as one workflow.The pipeline is three API calls: build_mosaic (planting and harvest Sentinel-2 median composites, 2023-01-01 to 2025-01-01, EPSG:4326), predict_mosaic, and vectorize_mosaic (field band, threshold 0.5). RasterFlow is backed by Flyte V2 and Ray, running in each organization's isolated namespace.Scale profile: feature COGs 153 TB (90,918 objects); feature Zarr mosaic 150 TB (363,999 objects, 7,499,140 logical chunks); prediction Zarr 45 TB (84,877 objects, 8,982,630 logical chunks); vector GeoParquet 675 GB (1,000 objects, 8,217,195,679 rows). Aggregate output ~348.7 TB across 540,794 objects.Zarr layout splits logical chunks from shards on purpose: feature logical chunks (1, 1, 4096, 4096) packed into (1, 1, 12288, 12288) shards; prediction logical chunks (1, 1, 2048, 2048) packed into (1, 3, 8192, 8192) shards covering all three class bands. That keeps regional reads precise without exploding object count.
Raster Processing at Scale: The Out-of-Database Architecture Behind WherobotsDB Posted on March 10, 2026October 4, 2026 by Pranav Toggi Introduction Raster data (satellite imagery, elevation models, sensor grids) is critical to understanding the physical world and increasingly to powering AI. The challenge most data teams face is processing it at scale. Processing raster data at scale requires an architecture that avoids loading entire files into memory. WherobotsDB solves this with an out-of-database approach that fetches pixel data on demand, enabling terabyte-scale processing without the memory overhead of traditional raster engines. WherobotsDB extends open-source Apache Sedona with capabilities and performance optimizations purpose-built for preparing physical-world data for AI at scale, while maintaining full API compatibility. Existing Sedona workloads run without code changes. This post covers how WherobotsDB handles the full raster lifecycle: scalable processing architecture, raster math, coordinate reference systems, vector-raster hybrid workflows, and planetary-scale inference. “With Wherobots, we were able to merge 15+ complex vector datasets in minutes and run high-resolution ML inference on raster imagery at a fraction of the cost of our legacy stack. The combination of speed, scalability, and ease of integration has boosted our engineering productivity and will accelerate how quickly we can deliver new geospatial data products to market.” — Rashmit Singh, CTO, SatSure What Is Out-of-Database Raster Architecture? At the foundation of Wherobots raster capabilities is an out-of-database raster architecture – which makes it far easier to process raster imagery in an embarrassingly parallel fashion. Instead of loading entire raster files into memory, only metadata is stored and pixel data is fetched on-demand. This means teams can process terabyte-scale imagery collections — statewide mosaics, multi-year satellite archives, continental elevation models — with the same interface they use for vector data. Operations like zonal statistics, clipping, masking, filtering, and raster algebra scale to datasets that would overwhelm in-memory approaches. Capability Apache Sedona WherobotsDB Notes Out-DB Raster Support ◔ ● Creates lightweight raster references; pixel data loaded only when needed Intelligent Caching Layer ○ ● Minimizes repeated remote reads for frequently-accessed rasters Optimized Shuffle Operations ○ ● Data movement handles only metadata — orders of magnitude faster than full rasters On-Demand Materialization ○ ● Selectively convert external rasters to in-database format when needed Automatic Metadata Optimization ○ ● Pre-loads metadata and intelligently repartitions for optimal parallelism Cloud-Optimized GeoTIFF Support ◐ ● Native COG support with tile-based partial reads from cloud storage How Out-DB Architecture Transforms Raster Operations The out-of-database architecture fundamentally changes how raster operations execute: Operation Apache Sedona (In-DB Only) WherobotsDB Data Movement (Shuffle) Serializes all pixel data across executors Serializes only metadata (~KB vs GB) Tiling Operations Copies pixel data into each tile Memory-efficient tiling without data duplication Clipping Full pixel-by-pixel processing Optimized processing paths for common operations Zonal Statistics Processes entire raster regardless of region size Materializes only zonal pixels, optimizing I/O based on region of interest Raster Loading Loads entire raster into memory at read time Lazy loading: metadata on-demand, pixels only when accessed Resource Management Standard memory lifecycle Intelligent caching layer with disk caching for remote files Key benefits: On-Demand Data Access: Instead of loading entire raster files into memory, WherobotsDB fetches pixel data only when an operation requires it, reducing memory overhead and enabling processing at terabyte scale. Memory-Efficient Tiling: Tiles share references to the underlying file with different spatial bounds — enabling massive parallelism without memory overhead. Smart I/O Reduction: Operations optimize I/O based on the region of interest, and spatial filter push-down skips irrelevant raster files entirely. Cloud-Optimized GeoTIFFs (COGs) are GeoTIFF files structured so that only the specific byte ranges needed for a given operation are fetched from remote storage, rather than downloading the entire file. Combined with on-demand loading, this architecture minimizes both memory footprint and network I/O. What Raster Capabilities Does WherobotsDB Include? Building on this foundation, WherobotsDB includes enhanced raster functions that enable satellite imagery, elevation models, and sensor data analysis directly in SQL alongside traditional vector operations. Capability Apache Sedona WherobotsDB Notes Raster to Vector Conversion ○ ● Convert raster regions to vector polygons for hybrid vector-raster analysis workflows Multi-Band Tile Processing ○ ● Align, stack, and tile rasters from different sources, CRS, and resolutions for distributed multi-source analysis Zonal Statistics ◕ ● Both support the full statistics suite; WherobotsDB’s Out-DB architecture materializes only zonal pixels, enabling scalability across millions of zones Custom Raster Algebra ◕ ● Flexible map algebra expressions with near-native execution performance Spatial Filter Push-down for Rasters ○ ● Uses bounding boxes to skip irrelevant raster files, dramatically reducing I/O for selective queries These capabilities fall into two categories: Transforming raster data for hybrid workflows – Raster to Vector Conversion, Multi-Band Tile Processing. Analyzing raster data in place – Zonal Statistics, Custom Raster Algebra, Spatial Filter Push-down. Raster to Vector Conversion converts contiguous raster regions with the same pixel value into vector polygons. Essential for workflows that need to analyze raster-derived features (flood extents, land cover classifications, building footprints from DSM) using vector spatial operations like overlay, buffering, or spatial joins. Multi-Band Tile Processing solves one of the most common friction points in raster analysis: combining data from different sources. Satellite imagery from different sensors, time periods, or providers typically arrives in different coordinate reference systems, resolutions, and data types. WherobotsDB aligns and stacks these into a unified multi-band raster, then tiles it into spatial chunks for distributed processing — all in a single operation. This enables workflows like change detection across multi-temporal composites, fusing Sentinel-2 optical bands with elevation data, or building analysis-ready multi-spectral stacks, without manual reprojection or resampling steps. Zonal Statistics computes aggregate statistics (count, sum, mean, median, mode, stddev, variance, min, max) for raster pixels falling within vector zones. Both Apache Sedona and WherobotsDB support zonal statistics — the differentiator is scale. WherobotsDB’s out-of-database architecture materializes only the pixels within each zone rather than processing the entire raster, making it practical to run zonal statistics across millions of zones on terabyte-scale imagery. Custom Raster Algebra executes user-defined raster algebra expressions with near-native execution performance. It supports complex multi-band calculations, conditional logic, and neighborhood operations — enabling workflows like computing NDVI (Normalized Difference Vegetation Index, a measure of vegetation density derived from red and near-infrared bands) from satellite imagery, or applying threshold-based classification across large imagery collections. Spatial Filter Push-down for Rasters uses bounding boxes to skip irrelevant raster files, dramatically reducing I/O for selective queries. When a catalog contains thousands of scenes but only a few intersect your area of interest, irrelevant files are eliminated before any processing begins. Because all of these functions are built on the out-of-database architecture, they inherit the same scalability characteristics described above — lazy loading, selective pixel materialization, and intelligent caching, without additional configuration. What Is RasterFlow and How Does It Work? Recently, Wherobots has added an entirely new inference and perception engine for planetary-scale image processing – extending the raster lifecycle beyond analysis into AI. RasterFlow enables teams to run computer vision models against large-scale raster datasets. From preparing imagery, mosaicking, removing edge effects across tiles, executing distributed model inference, and converting predictions into vector geometries, all within Wherobots Cloud. RasterFlow’s outputs are stored as vectorized results in Apache Iceberg tables — an open table format for large-scale analytic datasets — or as predictions within ZARR (a cloud-native format for chunked, compressed multi-dimensional arrays) or COGs, which can be seamlessly analyzed using the full suite of spatial operations in WherobotsDB. This creates end-to-end raster workflows — from raw imagery through model inference to spatial analytics, without moving data between systems or building custom infrastructure. RasterFlow supports both popular open-source geospatial AI models and custom PyTorch models, and can generate embeddings from geospatial foundation models. It is currently available to select customers in private preview. If you’re interested in RasterFlow, join our upcoming session to see it in action. What Comes Next: Query Performance and Spatial Analytics Raster processing is not only a first-class capability in WherobotsDB, but also it’s one part of a broader set of spatial data processing advances we’ve built beyond open-source Sedona. Vector and raster workloads both benefit from the same query performance optimizations under the hood: spatial relationship acceleration, automatic join optimization, dynamic data redistribution, and a vectorized GeoParquet reader. Queries that require careful tuning with self-managed Sedona run optimally out-of-the-box with WherobotsDB. In the next post in this series, we’ll go deep on query performance and spatial analytics, how WherobotsDB accelerates spatial joins, range queries, and analytical functions across both vector and raster data types. Start Building with Wherobots Access Now Key takeawaysWherobotsDB uses an out-of-database raster architecture: only metadata is stored, and pixel data is fetched on demand. That enables terabyte-scale processing (statewide mosaics, multi-year archives, continental elevation models) without loading entire files into memory. Existing Apache Sedona workloads run without code changes.Shuffles serialize only metadata (~KB vs GB of pixels). Tiles share file references with different spatial bounds. Zonal statistics materialize only zonal pixels. Spatial filter push-down skips irrelevant raster files using bounding boxes. Cloud-Optimized GeoTIFFs allow tile-based partial reads from cloud storage. An intelligent caching layer minimizes repeated remote reads.Capabilities called out beyond OSS Sedona include raster-to-vector conversion, multi-band tile processing (align/stack/tile rasters from different CRS and resolutions in one operation), custom raster algebra with near-native performance, and spatial filter push-down for rasters. Both engines support zonal statistics; the differentiator is out-DB scale across millions of zones.RasterFlow extends the raster lifecycle into inference: mosaicking, edge-effect handling, distributed model inference, and converting predictions to vectors, stored as Iceberg tables, Zarr, or COGs. It supports open geospatial AI models, custom PyTorch models, and embeddings from foundation models, and was in private preview at time of writing. SatSure's CTO is quoted on merging 15+ vector datasets in minutes and running high-resolution ML inference at a fraction of legacy cost.
Introducing RasterFlow: a planetary scale inference engine for Earth Intelligence Posted on December 10, 2025October 4, 2026 by Philip Darringer We’re very excited to announce RasterFlow is now available to select customers in a private preview. If you are interested in learning more or would like to request access to the preview, contact us here! RasterFlow is a serverless image preparation and inference engine that makes it significantly easier to generate Earth Intelligence from planetary scale Earth Observation (EO) datasets. With it, customers and their AI agents will be significantly more capable of innovating with EO data and integrating earth insights into their data infrastructure. Upcoming Session: See an AI agent take plain-language question and orchestrating pipelines to end results using RasterFlow and the Wherobots MCP server. See it in action Join us live How RasterFlow Powers Earth Intelligence at Scale A few weeks ago, we announced our collaboration with the Taylor Geospatial Engine to help them evaluate their Fields of the World (FTW) machine learning model that segments agricultural field boundaries. Using an early release of RasterFlow, we were able to quickly and cost-effectively run this model at scale. Here’s a breakdown of how this works in practice. RasterFlow ingests and assembles the source imagery – in this case Sentinel-2 – into an inference-ready mosaic, generating representative features using the FTW model for planting and harvest seasons, and removing cloud cover as needed (1, 2). The FTW model is run against this mosaic using RasterFlow’s distributed inference engine to predict fields and field boundaries (3). RasterFlow predictions are then vectorized into geometries and made available as an Iceberg table (4) that can be used in WherobotsDB or other downstream applications and data systems for field-level crop insights. RasterFlow’s applicability is much wider than Sentinel 2 and FTW. It supports Zarr and COG imagery datasets and PyTorch computer vision models for inference. RasterFlow at Scale The images above represent sample outputs for a small area in Kansas, but RasterFlow can be very attractive for larger scale runs. In our collaboration with the Taylor Geospatial Engine, we executed larger scale runs including the Continental United States (CONUS), Japan, Mexico, South Africa, Switzerland and Rwanda. RasterFlow’s efficient parallel processing enabled each of these large scale workflows to complete in minutes to a few hours. RasterFlow autoscales compute resources based on expected compute and inference load, which is a function of area and time range, dataset density, and model complexity. Challenges using EO Data Most data teams do not have the expertise or the budget to build and operate the unique infrastructure and software stack required to extract insights from EO datasets using computer vision models. These barriers have prevented innovative ideas from getting off the ground. According to Gartner, only 1% of AI models today leverage physical world data, vs a projected 80% by 2029. Similarly, AI agents are projected to generate 10 times more data from physical environments than from all digital AI applications combined.1 However, AI agents can’t economically make sense of this raw data because it has to be prepared by the same costly, complex, and unique infrastructure the data teams need, but neither have access to. Here’s an example that underscores these challenges: if you or an AI agent are trying to analyze wildfire state and predicted spread to measure risk to infrastructure, developers typically need to build dedicated pipelines that: Ingest and prepare imagery for inference, minimizing noise such as cloud cover and edge effects Deploy a machine learning model on prepared imagery, trained to segment and classify fires Tune model inference for scale and efficiency, while minimizing edge and tiling effects from individual tasks Measure change over time using models that take into account wind direction, speed, vegetation, buildings and other infrastructure in the probable path of the fire Join model predictions with other important context including building footprints, land parcels and infrastructure such as powerlines and pipelines to calculate overall risk Forecast the spread of the fire In total, these steps require significant investments in both infrastructure development, operations, and talent that most businesses are unable to justify, much even accomplish. On-Demand Imagery Preparation and Inference for Earth Observation Workflows The inspiration for RasterFlow was to make it easy for any company to use large scale sensor datasets and computer vision models to unblock innovation and AI applications for the physical world. RasterFlow does this by combining decades of expertise with a fully managed, inference and mosaicking workflow and API designed for Earth Intelligence at any scale. Here are a few key capabilities: On-demand serverless operations for imagery ingestion, preparation (also known as mosaicking), and inference. Built-in support for popular open datasets and open models so you can get started quickly. Inference results that can be converted to vector geometries and integrated into a lakehouse architecture; in a customer’s cloud storage bucket as Parquet files in Apache Iceberg tables. Ability to easily postprocess these results with WherobotsDB or other lakehouse engines with support for spatial operations, such as Databricks, Snowflake, or Google BigQuery. Simple enough for any engineer, scientist, or analyst to use: just pick a model, an area of interest to deploy that model, and a time range. Advanced users can take advantage of lower-level APIs to customize their planetary-scale inference runs. RasterFlow Operators: Core Functions for Preparing Imagery, Model Inference, and Vectorization RasterFlow provides fully managed operations required for processing Earth Observation datasets, including: Imagery ingestion and preparation to remove cloud cover, edge effects, and build a high quality inference-ready mosaic Distributed inference for large scale computer vision, geospatial foundational and other PyTorch model runs Vectorization of model outputs into geometries or as analytics ready rasters For object detection workloads, RasterFlow pairs with models like Segment Anything 3, see how SAM 3 performs on aerial and satellite imagery for a full walkthrough. Ingesting and Preparing Satellite Imagery for Model Inference Satellites and drones capture imagery on a particular flight path. And it may take multiple drone flights, or days, weeks, or even months for the flight paths of a satellite constellation to capture clean imagery for a particular area of interest. Clouds and weather events may still block what you may be interested in. In these circumstances it’s important to understand the rate of coverage and define your time horizon accordingly, to build a mosaic. A mosaic is a composite image that is the result of composing high-quality pixels (e.g., cloud free) over a time range, and stitching them together for a particular area. Base satellite layers in your favorite map applications (Google Maps, Mapbox) are cloud-free mosaics composed from images over a wide time range. Many computer vision models are trained to find relatively durable things on Earth, like buildings, roads, and land cover. But when clouds, coverage, imagery edge effects, or other types of “noise” exist in the input imagery, the quality of inference suffers. The purpose of the mosaic is to correct for this noise and make imagery, inference-ready, so model inference produces the results you want. RasterFlow takes care of this heavy lifting for you, creating an inference-ready mosaic that maximizes the usefulness of today’s Earth Observation models. Distributed Geospatial Inference at Planetary Scale We’ve moved past the use of eyes to analyze imagery, and are now capable of letting machines do this work for us. With RasterFlow, today’s machine learning models can perform tasks such as object detection, segmentation, and classification, on a very large area of interest, with orders of magnitude more efficiency and scale than an analyst’s eyes can offer. The RasterFlow inference engine is designed for small to very large scale runs. It efficiently parallelizes across the input mosaic across a distributed and serverless inference architecture while minimizing tiling effects typically produced when inference pipelines operate on individual tiles. Running Hosted or Custom Geospatial AI Models with RasterFlow For convenience, RasterFlow currently hosts popular open source PyTorch geospatial computer vision models that are ready to use. These models currently include: Fields of the World (FTW) Field Boundary Delineation Meta and World Resource Institute Tree Canopy Height Prediction ChesapeakeRSC Road Segmentation Tile2Net Pathway Segmentation You can also import your own custom PyTorch model to your Wherobots Organization for private deployment. RasterFlow + TorchGeo: Simplifying PyTorch-Based Geospatial AI Wherobots actively supports the TorchGeo project which helps machine learning experts to more easily work with geospatial data within the PyTorch ecosystem. We will continue to build out RasterFlow integrations with TorchGeo, including onboarding additional TorchGeo models and further simplifying the model lifecycle for PyTorch models. While we are starting with support for PyTorch focusing on TorchGeo models, we are open to adding support for other model frameworks. Calling Geospatial Model Developers: Contribute to RasterFlow We are continually adding new, open source geospatial computer vision models to the Wherobots Model Hub. And if you’re a model developer, we’re interested in speaking with you to onboard your model and distribute the value of your work to a wider audience using Wherobots RasterFlow. Vectorizing Model Outputs: From Raster Predictions to Geospatial Geometries Many computer vision models output rasters, where each pixel in the raster represents a predicted real-world value such as height of the tree canopy, or the confidence that the pixel represents a certain feature such as an agricultural field boundary or a sidewalk. RasterFlow provides built-in support for raster vectorization, turning pixel values into rich, concise geometries. These geometries represent features of interest that can be post-processed, conflated, and integrated into your workflows because they are yours, stored in open source file (Parquet) and table (Iceberg) formats in your S3 bucket. Using RasterFlow with Geospatial Foundation Models and Embeddings Recent developments in Geospatial Foundation Models have generated tremendous interest in the research community, potentially accelerating Earth Observation applications the same way that Large Language Models (LLMs) and embeddings have transformed AI’s ability to generate language. RasterFlow can generate embeddings from the latest open Geospatial Foundation Models, including OlmoEarth from the Allen Institute for AI (Ai2) and Clay. With RasterFlow’s ability to cost-effectively generate embeddings at scale, researchers and practitioners can easily generate embeddings for their area of interest and evaluate their suitability and power. Customers and Partners Using RasterFlow for Scalable Earth Intelligence One highlight while developing RasterFlow has been our collaboration with customers and partners like SatSure, Taylor Geospatial Engine, and Spyrosoft. We’ve used feedback from these teams to ensure we are solving for customer needs. Before the Thanksgiving holiday we shared our recent learnings from working together with Taylor Geospatial Engine, who have been incredibly helpful in providing input on the types of ways their ecosystem of developers and ML engineers would want to interact with RasterFlow. SatSure is an existing Wherobots customer and an early adopter of RasterFlow, and we are excited to see what they build next with it. "RasterFlow meaningfully accelerates the work SatSure and Wherobots already do together. By automating mosaicking, preprocessing, and distributed inference into a single, on-demand workflow, it removes much of the engineering overhead required to operationalize our models at national and multi-season scale. This helps us move new geospatial AI models into production faster, iterate more quickly with customers, and deliver fresher, high-resolution insights across agriculture, banking and financial services, and infrastructure use cases." Rashmit Singh CTO and co-founder, SatSure One of the largest deployments to date processed 348 TB of satellite imagery for a global release, see how RasterFlow delivered Fields of the World at planetary scale RasterFlow Availability and Multi-Cloud Architecture Wherobots infrastructure runs natively on AWS and customers pay for use through the AWS marketplace. RasterFlow and WherobotsDB support hybrid architectures, where data is read from, and results are written to other environments such as GCP, Azure, Oracle, or on-premises. This is particularly useful when processing open datasets or using open models and the environment in which data is processed may not be a concern. On-demand pricing for RasterFlow will be announced at a later date, but can be discussed with customers participating in the private preview. Next Steps: Try RasterFlow and Explore the Wherobots Spatial Data Platform We invite anyone who wants to test out RasterFlow to request to join the private preview here. Get started building with the most capable and efficient spatial data platform using the Wherobots Professional Edition. Sign up for the newsletter to keep pace with what’s happening at Wherobots. Source – 27 August 2025, Gartner Innovation Insight: World Models Are Set to Empower AI Agents With Imagination ↩︎ Join the Private Preview Sign Up Key takeawaysRasterFlow is a serverless imagery-preparation and inference engine, now in private preview, that turns planetary-scale Earth Observation datasets into Earth Intelligence without customers having to build their own mosaicking and model-serving stack.Working with the Taylor Geospatial Engine, RasterFlow ran Fields of the World inference across CONUS, Japan, Mexico, South Africa, Switzerland, and Rwanda. Those large-scale workflows completed in minutes to a few hours as compute autoscaled with area, time range, dataset density, and model complexity.It ingests Zarr and Cloud-Optimized GeoTIFF imagery, hosts PyTorch models including FTW field boundaries, Meta/WRI tree canopy height, ChesapeakeRSC road segmentation, and Tile2Net pathway segmentation, and lets organizations import their own private PyTorch models.Predictions are vectorized into geometries and written as Parquet files in Apache Iceberg tables in the customer S3 bucket, then post-processed in WherobotsDB or other lakehouse engines such as Databricks, Snowflake, or BigQuery.Gartner projects that only 1% of AI models leverage physical-world data today versus 80% by 2029, while AI agents are projected to generate 10 times more data from physical environments than from all digital AI applications combined. One of the largest RasterFlow deployments processed 348 TB of satellite imagery for a global release.
Wherobots and Taylor Geospatial Engine Bring Fields-of-the-World Models to Production Scale Posted on November 26, 2025October 4, 2026 by Ben Pruden Agriculture depends on timely, reliable insight into what’s happening on the ground—what’s being planted, what’s being harvested, and how fields evolve over time. The Fields of The World (FTW) project was created to support exactly this mission, by building a fully open ecosystem of labeled data, software, standards and models to create a reliable global map of agricultural field boundaries using AI and Earth Observation (EO) data. Over the past several months, Wherobots has been working closely with the Taylor Geospatial Engine (TGE) team driving the FTW project to turn high-performing research models into operational, production-scale data products. This collaboration builds on TGE’s broader effort to accelerate AI & EO development through connecting cutting edge research to real world needs. Learn more about FTW at fieldsofthe.world and about TGE’s agricultural AI initiatives here. Turning Fields-of-the-World Research Models Into Operational Pipelines FTW Phase 2 surfaced state-of-the-art computer vision models for interpreting Sentinel-2 time-series imagery and predicting locations of agricultural fields. But, research models alone aren’t enough to deliver real agricultural insight. They must be reproducible at scale, compute-efficient, and aligned with downstream applications. This is where Wherobots focused its efforts: optimizing inference pipelines, distributing computation efficiently, and generating data products that can serve as reliable foundations for agricultural modeling, monitoring, and analysis. TGE’s objective was simple: transform breakthrough research into something developers, end-users, and organizations can actually use. "This project is a testament to what happens when the academic community, nonprofits, and industry all pull in the same direction… Wherobots' ability to run these open-source models at scale makes it possible for the community's work to reach a global audience, and open access to predictions and mosaics ensures that more researchers and innovators can build on top of it." Jennifer Marcus Executive Director, Taylor Geospatial Engine Open-Sourced Seasonal Mosaics and Model Predictions Today, we’re releasing the first production-scale outputs from this collaboration: Sentinel-2 Seasonal Mosaics Cloud-free, analysis-ready mosaics tailored to key agricultural seasons—planting and harvest—built from Sentinel-2 imagery. These mosaics provide a consistent, high-quality foundation for model training, monitoring workflows, and large-scale geospatial analysis. FTW Phase 2 Model Predictions Per-pixel prediction outputs generated by running the top-performing FTW Phase 2 model across these seasonal mosaics, aligned with FTW’s agricultural labeling standards. Expand for caption This is an example output of the field boundary model in a large scale AOI in Japan and Mexico. The comparison is between the 2023 predictions and 2023 + 2024 in predictions (2024 in bright green). Both datasets are openly available on Source Cooperative: https://source.coop/wherobots/fields-of-the-world These resources are designed to make advanced agricultural AI more accessible—supporting innovation in food security, sustainability, and data-driven farming. The Wherobots AI for Earth team were critical in scaling inference with the new field boundary delineation models to a multi-country scale. The fact that they can mosaic multi-season Sentinel-2 imagery over millions of square kilometers, then run models over those mosaics, and organize the outputs in minutes for a few hundred dollars is nothing short of incredible. Making global scale analysis a routine pipeline instead of an enormous one-off effort, has the potential to change monitoring tasks far beyond field boundary analysis. Caleb Robinson, Principal Research Scientist, Microsoft AI for Good Enabled by the Spatial Intelligence Cloud This release also highlights the underlying engine that made it possible. The Wherobots Spatial Intelligence Cloud is built specifically for large-scale geospatial machine learning workflows—constructing analysis-ready mosaics, executing distributed model inference, and writing results into modern, cloud-native formats like Zarr with exceptional efficiency. And model outputs can be further processed and analyzed within WherobotsDB, using the power of Apache Sedona to refine the field geometries or calculate vegetative indices at scale. These capabilities are part of our suite of tools within our AI for Earth product area. Under the hood, the platform uses state-of-the-art tooling for raster processing, GPU-accelerated inference, chunk-aligned storage, and aggressive cost optimization. These choices allow us to run continental-scale pipelines at speeds and costs that would have been unimaginable even a few years ago. As agricultural AI models continue to grow in scope and resolution, this kind of infrastructure becomes essential. Our goal is to make it straightforward for teams to experiment, scale, and operationalize geospatial ML without needing to reinvent the entire data stack. One of the most important parts about what the Wherobots’ team has done is to make it easy to see where and how the model is failing. For example, the model's poor performance in Nevada and the tiling artifacts from the previous model runs led to important changes in how we should be training models. Nathan Jacobs Assistant Vice Provost for Digital Transformation, Washington University in St. Louis What’s Next This is just the beginning. We’re continuing to work with the FTW and TGE teams to expand coverage, operationalize more models, and build richer analysis-ready layers for the agricultural AI ecosystem. If you’re exploring geospatial ML, agricultural monitoring, or large-scale satellite data processing, we’re excited to see what you build with these new open datasets—and with the Wherobots AI for Earth capabilities in your pipeline. Getting in Touch about Wherobots AI for Earth If you would like to continue to stay up to date about the Wherobots AI for Earth capabilities for solving real world problems, or speak to the Wherobots team about utilizing these capabilities in production, please reach out to us here. Key takeawaysWherobots partnered with the Taylor Geospatial Engine to take Fields of the World (FTW) Phase 2 research models—state-of-the-art computer vision on Sentinel-2 time-series imagery—and turn them into operational, production-scale agricultural field-boundary pipelines.The first public release includes cloud-free Sentinel-2 seasonal mosaics for planting and harvest seasons plus per-pixel FTW Phase 2 model predictions aligned with FTW agricultural labeling standards.Both datasets are openly available on Source Cooperative at source.coop/wherobots/fields-of-the-world.Microsoft AI for Good researcher Caleb Robinson notes that Wherobots can mosaic multi-season Sentinel-2 imagery over millions of square kilometers, run the models, and organize outputs in minutes for a few hundred dollars.The Spatial Intelligence Cloud builds analysis-ready mosaics, runs distributed GPU inference, writes results to cloud-native formats such as Zarr, and lets teams refine field geometries or calculate vegetative indices in WherobotsDB with Apache Sedona.
Enabling Meta’s SAM 2 model for Geospatial AI on satellite imagery Posted on April 22, 2025October 3, 2026 by Tiffany Huynh Editor’s note: Raster inference is now part of RasterFlow, the Wherobots engine for running computer vision models on satellite and aerial imagery. UPDATE: Raster inference is included in Wherobots RasterFlow. See Wherobots Get Started with RasterFlow – Wherobots for the most up-to-date workflows. WherobotsAI Raster Inference makes it easy to create innovative solutions from large scale satellite and aerial imagery using SQL and Python, and geovision models you import or source off-the-shelf. Today, we’re excited to announce that Raster Inference now supports Meta’s Segment Anything 2 (SAM 2 ) model. With support for the SAM 2 model for geospatial AI in Raster Inference (in preview), you can now perform general purpose object detection and feature segmentation on terabyte scale collections of geospatial imagery, simply by describing what you’re looking for in plain text. You can now use Raster Inference to answer questions from satellite imagery like “find the container ships” or “segment crop circles” or ”segment pickleball courts” from very large areas of interest. Raster Inference results are stored as Iceberg tables in your S3 bucket, so you can easily join them with other data using WherobotsDB and continue your analysis inline with over 300+ features and functions optimized for spatial solution development. Click here to launch this interactive notebook Launch Notebook Scaling Segment Anything (SAM 2) for Large-Scale Image Prediction Foundation computer vision models like Segment Anything 2 revolutionized image understanding by allowing users to detect and delineate objects, typically from small scale images using simple point or text based prompts. However its been very hard, if not infeasible to use this model for large scale object detection on satellite imagery, like “find all of the container ships in the Pacific Ocean”. Raster Inference in Wherobots makes the SAM 2 model, along with other geospatial computer vision models (including custom), easier to deploy and run on terabyte scale inference runs using a cloud-parallel compute architecture. Such high scale and performance is necessary for extracting insights from high resolution satellite imagery data. Now, customers can prompt the SAM 2 model with English text, and explore use cases for AI on Earth observation AI models much faster than before. Use Cases for SAM 2 in WherobotsAI Raster Inference We launched support for SAM 2 in Raster Inference because we saw customers starting to use Raster Inference to explore the potential of geospatial AI for their use cases. Some did not have ML expertise, and experts just wanted a fast way to explore and test their ideas. Exploring is as easy as opening an example notebook, modifying a text prompt, and hitting run. You can work the tabular output of all Raster Inference runs inline using SQL or Python, and use Wherobots’ rich toolbox of spatial functions to accelerate the realization of value from complex spatial data. We believe this is a great general purpose AI tool for testing the feasibility of your ideas. From here, you can decide to invest in or find fine-tuned model alternatives that may be optimized for your earth observation use case. Just like LLMs you’ve probably interfaced with, you have to take quality, reliability, and risk into account before utilizing the results verbatim in an application. But even if they aren’t 100% correct, they offer immense productivity gains. And like we’ve seen with LLMs, text-promptable models like SAM 2 will evolve and get better over time, improving the direct utility of results and use cases that can serve out-of-the-box. Getting Started You can get started in a matter of minutes, without any infrastructure or model management overhead. Sign up for the Professional Edition of Wherobots on the AWS Marketplace. New customers receive up to $400 in free credits if they sign up before May 31st, 2025. Create a Tiny GPU Runtime. You may need to request a limit increase to access GPU runtimes. Open the Analyzing_Data/Raster_Text_To_Segments_Airplanes notebook Click on [run all cells] You can also learn more about running the SAM 2 model in Raster Inference using the Wherobots product documentation. Introducing Text-Prompts for Raster Inference Engineers who worked with SAM 2 might recognize that out of the box, SAM 2 does not support text-prompting. Instead, it supports automatic segment detection without categories, point prompts, and bounding box prompts to produce segment predictions. To enabled text-prompting in Raster Inference, we integrated a two-stage model pipeline combining Google Deepmind’s OWLv2 open-vocabulary object detector with SAM 2. This enables two new powerful functions: Text_To_BBoxes: Provide a text prompt (like 'solar panels'), and it returns georeferenced bounding boxes identifying potential matches across your imagery dataset. Text_To_Segments: Similar to the bounding box function, but it provides precise, georeferenced polygon segments outlining the boundaries of detected objects based on your text description. Using WherobotsDB, all results can be easily joined to other spatial and non-spatial data you may need to complete the solution. If your solution simply requires localizing and counting objects, use Text_To_BBoxes. If you need to know the size or shape of objects use Text_To_Segments. Accessing these capabilities is straightforward using either SQL or Python, fitting seamlessly into the your existing data analysis or data engineering workflows. Here’s how can predict solar panel geometries in SQL: -- Find bounding boxes for 'solar panels' CREATE OR REPLACE TEMP VIEW detected_bboxes AS SELECT raster_column, RS_TEXT_TO_BBOXES('SAM 2 ', raster_column, 'solar panels') AS detection_result FROM my_imagery_table; -- Find segments for 'large buildings' CREATE OR REPLACE TEMP VIEW detected_segments AS SELECT raster_column, RS_TEXT_TO_SEGMENTS('SAM 2 ', raster_column, 'solar panels') AS segmentation_result FROM my_imagery_table; And in Python: # Find bounding boxes for 'solar panels' df_bboxes = df_raster_input.withColumn( "detection_result", rs_text_to_bboxes("SAM 2 ", col("raster_column"), "solar panels") ) # Find segments for 'large buildings' df_segments = df_raster_input.withColumn( "segmentation_result", rs_text_to_segments("SAM 2 ", col("raster_column"), "solar panels") ) Behind the scenes, Raster Inference handles the heavy lifting. It scales your text-prompted inference request and model across a distributed GPU runtime optimized for loading large geospatial raster datasets, and ensures work is balanced across serverless compute nodes. It automatically handles varying image sizes via padding and other techniques, and efficiently reads your raster data using features like limit and sample pushdown. And the results – whether bounding boxes or polygons – are georeferenced, ready for immediate use in SQL or Python based analysis or visualization with helper functions like show_detections. Improving SAM 2 for Geospatial Object Detection We’re still exploring the model’s capabilities, which is why the SAM 2 integration is in preview. Here are a few considerations we’ve identified based on observations during testing: The underlying OWLv2 model that powers the Text_To_Segments function wasn’t trained on a lot of satellite imagery examples so it can miss or incorrectly label visual features that are specific to satellite or aerial images. SAM 2 and OwLv2 might get confused with the concept of spatial scale. A prompt for “buildings” may return individual building segments or segments of entire neighborhoods. Inference performs well on distinct features in relatively simple visual environments, with minimal occlusion from clouds, shadows, or dense vegetation. SAM 2 performs well on high resolution NAIP 30cm imagery on simpler object categories, like airplane segmentation. Example: The OWLv2 object detection model (which Text_To_Segments depends on) can confuse visually similar features, like agriculture and solar farms, when prompted to detect “solar farms”. We are investigating paths to improve SAM 2’s accuracy, including supporting different prompting methods, such as using points to guide segmentation. Transforming Earth Observation with Geospatial AI Support for the SAM 2 model for geospatial AI in Raster Inference reflects on our core mission, which is to make it easy to utilize geospatial data at any scale. It also offers a tangible view into a future where interacting with vast archives of Earth Observation data is much easier and intuitive. We will continue to make SAM 2 and other geospatial models more accessible, and easier to use so customers can address diverse challenges solved with the help of Earth observation data. Tell Us What You Think Feel free to contact the team directly (product@wherobots.com) if you have feedback to share. We can’t wait to see what you discover! Get Started with RasterFlow START BUILDING Key takeawaysWherobotsAI Raster Inference (now part of RasterFlow) added preview support for Meta Segment Anything 2, so you can run general-purpose object detection and segmentation on terabyte-scale satellite and aerial imagery from a plain-English prompt.SAM 2 out of the box does not take text prompts. Raster Inference chains Google DeepMind OWLv2 open-vocabulary detector with SAM 2 to expose RS_TEXT_TO_BBOXES / Text_To_BBoxes and RS_TEXT_TO_SEGMENTS / Text_To_Segments in SQL and Python.Use bounding boxes when you only need to localize and count objects; use segments when you need size or shape. Results are georeferenced and stored as Iceberg tables in your S3 bucket for joins with WherobotsDB 300+ spatial functions.Get started on Professional Edition via AWS Marketplace (the post offered up to $400 in credits if you signed up before May 31, 2025), create a Tiny GPU Runtime, and run the Raster_Text_To_Segments_Airplanes example notebook.Preview limitations: OWLv2 was not trained on much satellite imagery, scale can confuse buildings vs neighborhoods, and quality is best on distinct features in simple scenes. The post shows strong airplane segmentation on NAIP 30 cm imagery and confusion between agriculture and solar farms on a solar farms prompt.