Skip to content

Commit 4dd60a0

Browse files
authored
Merge pull request #105 from allenai/favyen/sentinel2_detector_update
Add API for Sentinel-2 vessel detection
2 parents ca7acf7 + a233671 commit 4dd60a0

File tree

15 files changed

+877
-173
lines changed

15 files changed

+877
-173
lines changed

.github/workflows/landsat_vessel.yaml

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,9 @@ on:
66
tags:
77
- "landsat_vessels_v*" # Trigger only when a version tag (e.g., landsat_vessels_v0.0.1) is pushed
88

9-
env:
10-
REGISTRY: ghcr.io
11-
IMAGE_NAME: "landsat-vessel-detection"
12-
SERVICE_NAME: "landsat_vessels"
13-
ORG_NAME: "allenai"
14-
159
jobs:
1610
build-and-push:
17-
runs-on: ubuntu-latest
18-
permissions:
19-
contents: read
20-
packages: write
21-
steps:
22-
# Step 1: Checkout the repository and fetch all tags
23-
- name: Checkout repository
24-
uses: actions/checkout@v4
25-
with:
26-
fetch-depth: 0 # Ensure all history and tags are fetched
27-
28-
- name: Fetch tags
29-
run: git fetch --tags --force
30-
31-
# Step 2: Extract the version from the latest tag and its associated commit SHA
32-
- name: Get latest tag and associated SHA
33-
id: version
34-
run: |
35-
LATEST_TAG=$(git tag --list "${{ env.SERVICE_NAME }}_v*" --sort=-v:refname | head -n 1)
36-
TAG_COMMIT=$(git rev-list -n 1 $LATEST_TAG)
37-
SHORT_SHA=$(git rev-parse --short $TAG_COMMIT)
38-
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
39-
echo "SHORT_SHA=$SHORT_SHA" >> $GITHUB_ENV
40-
41-
# Step 3: Log in to GHCR
42-
- name: Log in to GitHub Container Registry
43-
uses: docker/login-action@v3
44-
with:
45-
registry: ${{ env.REGISTRY }}
46-
username: ${{ github.actor }}
47-
password: ${{ secrets.GITHUB_TOKEN }}
48-
49-
# Step 4: Build and Push Docker Image
50-
- name: Build and Push Docker Image
51-
working-directory: rslp/${{ env.SERVICE_NAME }}
52-
run: |
53-
docker compose build
54-
docker tag ${{ env.SERVICE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.IMAGE_NAME }}:sha-${{ env.SHORT_SHA }}
55-
docker tag ${{ env.SERVICE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
56-
docker push ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.IMAGE_NAME }}:sha-${{ env.SHORT_SHA }}
57-
docker push ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
11+
uses: allenai/rslearn_projects/.github/workflows/publish_project_docker_image.yaml@master
12+
with:
13+
rslp_project: "landsat_vessels"
14+
image_name: "landsat-vessel-detection"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Publish-Project-Docker-Image
2+
3+
# This is a reusable workflow called by project-specific workflows to build and publish
4+
# a Docker image on Github Container Registry (GHCR).
5+
on:
6+
workflow_call:
7+
inputs:
8+
rslp_project:
9+
required: true
10+
type: string
11+
image_name:
12+
required: true
13+
type: string
14+
15+
env:
16+
REGISTRY: ghcr.io
17+
ORG_NAME: "allenai"
18+
19+
jobs:
20+
build-and-push:
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
packages: write
25+
steps:
26+
# Step 1: Checkout the repository and fetch all tags
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0 # Ensure all history and tags are fetched
31+
32+
- name: Fetch tags
33+
run: git fetch --tags --force
34+
35+
# Step 2: Extract the version from the latest tag and its associated commit SHA
36+
- name: Get latest tag and associated SHA
37+
id: version
38+
run: |
39+
LATEST_TAG=$(git tag --list "${{ inputs.rslp_project }}_v*" --sort=-v:refname | head -n 1)
40+
TAG_COMMIT=$(git rev-list -n 1 $LATEST_TAG)
41+
SHORT_SHA=$(git rev-parse --short $TAG_COMMIT)
42+
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
43+
echo "SHORT_SHA=$SHORT_SHA" >> $GITHUB_ENV
44+
45+
# Step 3: Log in to GHCR
46+
- name: Log in to GitHub Container Registry
47+
uses: docker/login-action@v3
48+
with:
49+
registry: ${{ env.REGISTRY }}
50+
username: ${{ github.actor }}
51+
password: ${{ secrets.GITHUB_TOKEN }}
52+
53+
# Step 4: Build and Push Docker Image
54+
- name: Build and Push Docker Image
55+
working-directory: rslp/${{ inputs.rslp_project }}
56+
run: |
57+
docker compose build
58+
docker tag ${{ inputs.rslp_project }}:latest ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ inputs.image_name }}:sha-${{ env.SHORT_SHA }}
59+
docker tag ${{ inputs.rslp_project }}:latest ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ inputs.image_name }}:${{ env.LATEST_TAG }}
60+
docker push ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ inputs.image_name }}:sha-${{ env.SHORT_SHA }}
61+
docker push ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ inputs.image_name }}:${{ env.LATEST_TAG }}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Sentinel2-Vessel-Detection
2+
3+
on:
4+
workflow_dispatch: # Manual trigger
5+
push:
6+
tags:
7+
- "sentinel2_vessels_v*" # Trigger only when a version tag (e.g., sentinel2_vessels_v0.0.1) is pushed
8+
9+
jobs:
10+
build-and-push:
11+
uses: allenai/rslearn_projects/.github/workflows/publish_project_docker_image.yaml@favyen/sentinel2_detector_update
12+
with:
13+
rslp_project: "sentinel2_vessels"
14+
image_name: "sentinel2-vessel-detection"

ai2_docs/landsat_vessels/api_use.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Prebuilt Docker images are available on GHCR. Use the following steps to pull an
5151
1. Pull the image from GHCR.
5252

5353
```bash
54-
docker pull ghcr.io/allenai/landsat-vessel-detection:v0.0.3
54+
docker pull ghcr.io/allenai/landsat-vessel-detection:landsat_vessels_v0.0.3
5555
```
5656

5757
2. Run the container. Note that you need to replace the `<port_number>` and `<path_to_service_account_key>` with the actual `LANDSAT_PORT` (if you use the default port, set it to `5555`) and path to your local service account key file, and keep the other arguments unchanged.
@@ -64,7 +64,7 @@ Prebuilt Docker images are available on GHCR. Use the following steps to pull an
6464
--env-file .env \
6565
--shm-size=15g \
6666
--gpus all \
67-
ghcr.io/allenai/landsat-vessel-detection:v0.0.3
67+
ghcr.io/allenai/landsat-vessel-detection:landsat_vessels_v0.0.3
6868
```
6969

7070
## Making Requests to the API

data/sentinel2_vessels/config.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ model:
3535
data:
3636
class_path: rslearn.train.data_module.RslearnDataModule
3737
init_args:
38-
path: gs://rslearn-eai/datasets/sentinel2_vessels/dataset_v1/20240927/
38+
path: gs://rslearn-eai/datasets/sentinel2_vessels/dataset_v1/20250213/
3939
inputs:
4040
image:
4141
data_type: "raster"
@@ -66,8 +66,10 @@ data:
6666
box_size: 15
6767
remap_values: [[0, 1], [0, 255]]
6868
exclude_by_center: true
69+
score_threshold: 0.7
6970
enable_map_metric: true
7071
enable_f1_metric: true
72+
f1_metric_thresholds: [[0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95], [0.1], [0.2], [0.3], [0.4], [0.5], [0.6], [0.7], [0.8], [0.9]]
7173
f1_metric_kwargs:
7274
cmp_mode: "distance"
7375
cmp_threshold: 15
@@ -122,7 +124,7 @@ trainer:
122124
logging_interval: "epoch"
123125
- class_path: rslearn.train.prediction_writer.RslearnWriter
124126
init_args:
125-
path: gs://rslearn-eai/datasets/sentinel2_vessels/dataset_v1/20240927/
127+
path: gs://rslearn-eai/datasets/sentinel2_vessels/dataset_v1/20250213/
126128
output_layer: output
127129
selector: ["detect"]
128130
merger:
@@ -138,5 +140,9 @@ trainer:
138140
save_last: true
139141
monitor: val_detect/mAP
140142
mode: max
143+
- class_path: rslearn.train.callbacks.freeze_unfreeze.FreezeUnfreeze
144+
init_args:
145+
module_selector: ["model", "encoder", 0, "model"]
146+
unfreeze_at_epoch: 4
141147
rslp_project: sentinel2_vessels
142-
rslp_experiment: data_20240927_satlaspretrain_patch512_00
148+
rslp_experiment: data_20240213_01_add_freezing_and_fix_fpn_restore
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"layers": {
3+
"label": {
4+
"type": "vector"
5+
},
6+
"mask": {
7+
"band_sets": [
8+
{
9+
"bands": [
10+
"mask"
11+
],
12+
"dtype": "uint8",
13+
"format": {
14+
"format": "png",
15+
"name": "single_image"
16+
}
17+
}
18+
],
19+
"type": "raster"
20+
},
21+
"output": {
22+
"type": "vector"
23+
},
24+
"sentinel2": {
25+
"band_sets": [
26+
{
27+
"bands": [
28+
"R",
29+
"G",
30+
"B"
31+
],
32+
"dtype": "uint8",
33+
"format": {
34+
"name": "geotiff"
35+
}
36+
}
37+
],
38+
"data_source": {
39+
"item_specs": "PLACEHOLDER",
40+
"name": "rslearn.data_sources.local_files.LocalFiles",
41+
"src_dir": "PLACEHOLDER"
42+
},
43+
"type": "raster"
44+
}
45+
},
46+
"tile_store": {
47+
"name": "file",
48+
"root_dir": "tiles"
49+
}
50+
}

docs/sentinel2_vessels.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ First, download the model checkpoint to the `RSLP_PREFIX` directory.
2020

2121
cd rslearn_projects
2222
mkdir -p project_data/projects/sentinel2_vessels/data_20240927_satlaspretrain_patch512_00/checkpoints/
23-
wget https://storage.googleapis.com/ai2-rslearn-projects-data/sentinel2_vessels/best.ckpt -O project_data/projects/sentinel2_vessels/data_20240927_satlaspretrain_patch512_00/checkpoints/best.ckpt
23+
wget https://storage.googleapis.com/ai2-rslearn-projects-data/projects/sentinel2_vessels/data_20240213_01_add_freezing_and_fix_fpn_restore/checkpoints/best.ckpt -O project_data/projects/sentinel2_vessels/data_20240213_01_add_freezing_and_fix_fpn_restore/checkpoints/best.ckpt
2424

2525
The easiest way to apply the model is using the prediction pipeline in
2626
`rslp/sentinel2_vessels/predict_pipeline.py`. It accepts a Sentinel-2 scene ID and
@@ -65,3 +65,82 @@ To visualize outputs on the validation set:
6565

6666
mkdir vis
6767
python -m rslp.rslearn_main model test --config data/sentinel2_vessels/config.yaml --data.init_args.path project_data/datasets/sentinel2_vessels/ --model.init_args.visualize_dir vis/ --load_best true
68+
69+
70+
Model Version History
71+
---------------------
72+
73+
The version names correspond to the `rslp_experiment` field in the model configuration
74+
file (`data/sentinel2_vessels/config.yaml`).
75+
76+
- `data_20240213_01_add_freezing_and_fix_fpn_restore`: Freeze the pre-trained model for
77+
the first few epochs before unfreezing.
78+
- `data_20240213_00`: Some of the windows contained blank images. I re-ingested the
79+
dataset and the issue seems to be fixed. The model is re-trained.
80+
- `data_20240927_satlaspretrain_patch512_00`: initial model.
81+
82+
83+
Model Performance
84+
-----------------
85+
86+
### data_20240213_01_add_freezing_and_fix_fpn_restore
87+
88+
- Selected threshold: 0.8
89+
- Results on validation set (split1, split7, sargassum_val)
90+
- Precision: 78.0%
91+
- Recall: 77.6%
92+
- Note it should be 20250213 but there is typo.
93+
94+
Docker Container with FastAPI
95+
-----------------------------
96+
97+
We also have a Docker container that exposes a FastAPI interface to apply vessel
98+
detection on Sentinel-2 scenes. This section explains how to setup the API.
99+
100+
### Run the Docker container
101+
102+
The Docker container does not contain the model weights. Instead, it expects the model
103+
weights to be present in a directory based on the `RSLP_PREFIX` environment variable.
104+
So download the model checkpoint:
105+
106+
mkdir -p project_data/projects/sentinel2_vessels/data_20240213_01_add_freezing_and_fix_fpn_restore/checkpoints/
107+
wget https://storage.googleapis.com/ai2-rslearn-projects-data/projects/sentinel2_vessels/data_20240213_01_add_freezing_and_fix_fpn_restore/checkpoints/best.ckpt -O project_data/projects/sentinel2_vessels/data_20240213_01_add_freezing_and_fix_fpn_restore/checkpoints/best.ckpt
108+
109+
Run the container:
110+
111+
```bash
112+
export SENTINEL2_PORT=5555
113+
docker run \
114+
--rm -p $SENTINEL2_PORT:$SENTINEL2_PORT \
115+
-e RSLP_PREFIX=/project_data \
116+
-e SENTINEL2_PORT=$SENTINEL2_PORT \
117+
-v $PWD/project_data/:/project_data/ \
118+
--shm-size=15g \
119+
--gpus all \
120+
ghcr.io/allenai/sentinel2-vessel-detection:sentinel2_vessels_v0.0.1
121+
```
122+
123+
### Auto Documentation
124+
125+
This API has enabled Swagger UI (`http://<your_address>:<port_number>/docs`) and ReDoc (`http://<your_address>:<port_number>/redoc`).
126+
127+
### Making Requests
128+
129+
Process a scene by its Sentinel-2 scene ID. Note that the crop path is optional.
130+
131+
```bash
132+
curl -X POST http://localhost:${SENTINEL2_PORT}/detections -H "Content-Type: application/json" -d '{"scene_id": "S2A_MSIL1C_20180904T110621_N0206_R137_T30UYD_20180904T133425", "crop_path": "crops/"}'
133+
```
134+
135+
The API will respond with the vessel detection results in JSON format.
136+
137+
Alternatively, process the scene by providing the paths to the image assets. The paths
138+
can be URIs but must be accessible from the Docker container.
139+
140+
```bash
141+
curl -X POST http://localhost:${SENTINEL2_PORT}/detections -H "Content-Type: application/json" -d '{"image_files": [{"bands": ["R", "G", "B"], "fname": "gs://gcp-public-data-sentinel-2/tiles/30/U/YD/S2A_MSIL1C_20180904T110621_N0206_R137_T30UYD_20180904T133425.SAFE/GRANULE/L1C_T30UYD_A016722_20180904T110820/IMG_DATA/T30UYD_20180904T110621_TCI.jp2"}, {"bands": ["B08"], "fname": "gs://gcp-public-data-sentinel-2/tiles/30/U/YD/S2A_MSIL1C_20180904T110621_N0206_R137_T30UYD_20180904T133425.SAFE/GRANULE/L1C_T30UYD_A016722_20180904T110820/IMG_DATA/T30UYD_20180904T110621_B08.jp2"}]}'
142+
```
143+
144+
### Docker Container Version History
145+
146+
- v0.0.1: initial version. It uses model `data_20240213_01_add_freezing_and_fix_fpn_restore`.

rslp/landsat_vessels/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
LANDSAT_LAYER_NAME = "landsat"
77
OUTPUT_LAYER_NAME = "output"
88
LANDSAT_RESOLUTION = 15
9-
LANDSAT_SOURCE = "landsat"
109

1110
# Data config
1211
LOCAL_FILES_DATASET_CONFIG = "data/landsat_vessels/predict_dataset_config.json"

rslp/landsat_vessels/predict_pipeline.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
LANDSAT_BANDS,
3232
LANDSAT_LAYER_NAME,
3333
LANDSAT_RESOLUTION,
34-
LANDSAT_SOURCE,
3534
LOCAL_FILES_DATASET_CONFIG,
3635
OUTPUT_LAYER_NAME,
3736
)
@@ -46,7 +45,7 @@
4645
materialize_dataset,
4746
run_model_predict,
4847
)
49-
from rslp.vessels import VesselDetection
48+
from rslp.vessels import VesselDetection, VesselDetectionSource
5049

5150
logger = get_logger(__name__)
5251

@@ -145,7 +144,7 @@ def get_vessel_detections(
145144
score = feature.properties["score"]
146145

147146
detection = VesselDetection(
148-
source=LANDSAT_SOURCE,
147+
source=VesselDetectionSource.LANDSAT,
149148
col=int(geometry.shp.centroid.x),
150149
row=int(geometry.shp.centroid.y),
151150
projection=geometry.projection,

rslp/sentinel2_vessels/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Base image
2+
FROM base-image:latest
3+
4+
# Environment variables
5+
ENV PYTHONPATH="/opt/rslearn_projects:${PYTHONPATH}"
6+
ENV SENTINEL2_PORT=5555
7+
8+
# Make port 5555 available to the world outside this container
9+
EXPOSE $SENTINEL2_PORT
10+
11+
# Run app.py when the container launches
12+
CMD ["python3", "rslp/sentinel2_vessels/api_main.py"]

0 commit comments

Comments
 (0)