rslearn 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. {rslearn-0.0.2/rslearn.egg-info → rslearn-0.0.3}/PKG-INFO +355 -278
  2. rslearn-0.0.3/README.md +508 -0
  3. rslearn-0.0.3/pyproject.toml +106 -0
  4. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/config/__init__.py +2 -2
  5. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/config/dataset.py +156 -99
  6. rslearn-0.0.3/rslearn/const.py +17 -0
  7. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/aws_landsat.py +216 -70
  8. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/aws_open_data.py +64 -78
  9. rslearn-0.0.3/rslearn/data_sources/aws_sentinel1.py +142 -0
  10. rslearn-0.0.3/rslearn/data_sources/climate_data_store.py +303 -0
  11. rslearn-0.0.3/rslearn/data_sources/copernicus.py +973 -0
  12. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/data_source.py +2 -4
  13. rslearn-0.0.3/rslearn/data_sources/earthdaily.py +489 -0
  14. rslearn-0.0.3/rslearn/data_sources/earthdata_srtm.py +300 -0
  15. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/gcp_public_data.py +435 -159
  16. rslearn-0.0.3/rslearn/data_sources/google_earth_engine.py +637 -0
  17. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/local_files.py +97 -74
  18. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/openstreetmap.py +6 -16
  19. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/planet.py +7 -26
  20. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/planet_basemap.py +52 -59
  21. rslearn-0.0.3/rslearn/data_sources/planetary_computer.py +764 -0
  22. rslearn-0.0.3/rslearn/data_sources/raster_source.py +23 -0
  23. rslearn-0.0.3/rslearn/data_sources/usda_cdl.py +206 -0
  24. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/usgs_landsat.py +84 -56
  25. rslearn-0.0.3/rslearn/data_sources/utils.py +319 -0
  26. rslearn-0.0.3/rslearn/data_sources/worldcereal.py +456 -0
  27. rslearn-0.0.3/rslearn/data_sources/worldcover.py +142 -0
  28. rslearn-0.0.3/rslearn/data_sources/worldpop.py +156 -0
  29. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/xyz_tiles.py +141 -78
  30. rslearn-0.0.3/rslearn/dataset/__init__.py +12 -0
  31. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/dataset/dataset.py +28 -3
  32. rslearn-0.0.3/rslearn/dataset/index.py +173 -0
  33. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/dataset/manage.py +129 -46
  34. rslearn-0.0.3/rslearn/dataset/materialize.py +591 -0
  35. rslearn-0.0.3/rslearn/dataset/window.py +379 -0
  36. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/main.py +235 -78
  37. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/clip.py +2 -2
  38. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/conv.py +7 -7
  39. rslearn-0.0.3/rslearn/models/croma.py +270 -0
  40. rslearn-0.0.3/rslearn/models/detr/__init__.py +5 -0
  41. rslearn-0.0.3/rslearn/models/detr/box_ops.py +103 -0
  42. rslearn-0.0.3/rslearn/models/detr/detr.py +493 -0
  43. rslearn-0.0.3/rslearn/models/detr/matcher.py +107 -0
  44. rslearn-0.0.3/rslearn/models/detr/position_encoding.py +114 -0
  45. rslearn-0.0.3/rslearn/models/detr/transformer.py +429 -0
  46. rslearn-0.0.3/rslearn/models/detr/util.py +24 -0
  47. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/faster_rcnn.py +8 -0
  48. rslearn-0.0.3/rslearn/models/module_wrapper.py +91 -0
  49. rslearn-0.0.3/rslearn/models/moe/distributed.py +262 -0
  50. rslearn-0.0.3/rslearn/models/moe/soft.py +676 -0
  51. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/molmo.py +2 -2
  52. rslearn-0.0.3/rslearn/models/multitask.py +392 -0
  53. rslearn-0.0.3/rslearn/models/pick_features.py +46 -0
  54. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/simple_time_series.py +14 -4
  55. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/singletask.py +8 -4
  56. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/ssl4eo_s12.py +1 -1
  57. rslearn-0.0.3/rslearn/models/task_embedding.py +250 -0
  58. rslearn-0.0.3/rslearn/models/terramind.py +219 -0
  59. rslearn-0.0.3/rslearn/models/trunk.py +280 -0
  60. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/unet.py +17 -3
  61. rslearn-0.0.3/rslearn/models/use_croma.py +508 -0
  62. rslearn-0.0.3/rslearn/py.typed +0 -0
  63. rslearn-0.0.3/rslearn/tile_stores/__init__.py +71 -0
  64. rslearn-0.0.3/rslearn/tile_stores/default.py +382 -0
  65. rslearn-0.0.3/rslearn/tile_stores/tile_store.py +328 -0
  66. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/callbacks/freeze_unfreeze.py +29 -17
  67. rslearn-0.0.3/rslearn/train/callbacks/gradients.py +109 -0
  68. rslearn-0.0.3/rslearn/train/callbacks/peft.py +116 -0
  69. rslearn-0.0.3/rslearn/train/data_module.py +562 -0
  70. rslearn-0.0.3/rslearn/train/dataset.py +1183 -0
  71. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/lightning_module.py +151 -45
  72. rslearn-0.0.3/rslearn/train/optimizer.py +31 -0
  73. rslearn-0.0.3/rslearn/train/prediction_writer.py +335 -0
  74. rslearn-0.0.3/rslearn/train/scheduler.py +62 -0
  75. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/classification.py +4 -4
  76. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/detection.py +27 -22
  77. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/multi_task.py +23 -8
  78. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/regression.py +105 -17
  79. rslearn-0.0.3/rslearn/train/tasks/segmentation.py +547 -0
  80. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/task.py +2 -2
  81. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/concatenate.py +3 -3
  82. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/crop.py +2 -2
  83. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/normalize.py +25 -5
  84. rslearn-0.0.3/rslearn/train/transforms/transform.py +131 -0
  85. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/__init__.py +0 -3
  86. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/feature.py +1 -1
  87. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/geometry.py +60 -5
  88. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/get_utm_ups_crs.py +2 -3
  89. rslearn-0.0.3/rslearn/utils/jsonargparse.py +33 -0
  90. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/raster_format.py +207 -100
  91. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/vector_format.py +142 -80
  92. {rslearn-0.0.2 → rslearn-0.0.3/rslearn.egg-info}/PKG-INFO +355 -278
  93. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn.egg-info/SOURCES.txt +31 -4
  94. rslearn-0.0.3/rslearn.egg-info/requires.txt +49 -0
  95. rslearn-0.0.2/README.md +0 -447
  96. rslearn-0.0.2/extra_requirements.txt +0 -17
  97. rslearn-0.0.2/pyproject.toml +0 -50
  98. rslearn-0.0.2/requirements.txt +0 -15
  99. rslearn-0.0.2/rslearn/const.py +0 -23
  100. rslearn-0.0.2/rslearn/data_sources/copernicus.py +0 -188
  101. rslearn-0.0.2/rslearn/data_sources/google_earth_engine.py +0 -300
  102. rslearn-0.0.2/rslearn/data_sources/raster_source.py +0 -319
  103. rslearn-0.0.2/rslearn/data_sources/utils.py +0 -131
  104. rslearn-0.0.2/rslearn/dataset/__init__.py +0 -6
  105. rslearn-0.0.2/rslearn/dataset/materialize.py +0 -271
  106. rslearn-0.0.2/rslearn/dataset/window.py +0 -193
  107. rslearn-0.0.2/rslearn/models/module_wrapper.py +0 -43
  108. rslearn-0.0.2/rslearn/models/multitask.py +0 -65
  109. rslearn-0.0.2/rslearn/models/pick_features.py +0 -33
  110. rslearn-0.0.2/rslearn/tile_stores/__init__.py +0 -37
  111. rslearn-0.0.2/rslearn/tile_stores/file.py +0 -245
  112. rslearn-0.0.2/rslearn/tile_stores/tile_store.py +0 -236
  113. rslearn-0.0.2/rslearn/train/data_module.py +0 -170
  114. rslearn-0.0.2/rslearn/train/dataset.py +0 -680
  115. rslearn-0.0.2/rslearn/train/prediction_writer.py +0 -189
  116. rslearn-0.0.2/rslearn/train/tasks/segmentation.py +0 -233
  117. rslearn-0.0.2/rslearn/train/transforms/transform.py +0 -129
  118. rslearn-0.0.2/rslearn/utils/utils.py +0 -30
  119. rslearn-0.0.2/rslearn.egg-info/requires.txt +0 -33
  120. {rslearn-0.0.2 → rslearn-0.0.3}/LICENSE +0 -0
  121. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/__init__.py +0 -0
  122. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/__init__.py +0 -0
  123. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/geotiff.py +0 -0
  124. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/data_sources/vector_source.py +0 -0
  125. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/dataset/add_windows.py +0 -0
  126. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/dataset/remap.py +0 -0
  127. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/log_utils.py +0 -0
  128. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/__init__.py +0 -0
  129. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/fpn.py +0 -0
  130. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/pooling_decoder.py +0 -0
  131. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/registry.py +0 -0
  132. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/sam2_enc.py +0 -0
  133. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/satlaspretrain.py +0 -0
  134. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/swin.py +0 -0
  135. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/models/upsample.py +0 -0
  136. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/__init__.py +0 -0
  137. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/callbacks/__init__.py +0 -0
  138. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/tasks/__init__.py +0 -0
  139. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/__init__.py +0 -0
  140. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/flip.py +0 -0
  141. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/train/transforms/pad.py +0 -0
  142. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/array.py +0 -0
  143. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/fsspec.py +0 -0
  144. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/grid_index.py +0 -0
  145. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/mp.py +0 -0
  146. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/rtree_index.py +0 -0
  147. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/spatial_index.py +0 -0
  148. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/sqlite_index.py +0 -0
  149. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn/utils/time.py +0 -0
  150. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn.egg-info/dependency_links.txt +0 -0
  151. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn.egg-info/entry_points.txt +0 -0
  152. {rslearn-0.0.2 → rslearn-0.0.3}/rslearn.egg-info/top_level.txt +0 -0
  153. {rslearn-0.0.2 → rslearn-0.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: rslearn
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A library for developing remote sensing datasets and models
5
5
  Author-email: Favyen Bastani <favyenb@allenai.org>, Yawen Zhang <yawenz@allenai.org>, Patrick Beukema <patrickb@allenai.org>, Henry Herzog <henryh@allenai.org>, Piper Wolters <piperw@allenai.org>
6
6
  License: Apache License
@@ -205,41 +205,57 @@ License: Apache License
205
205
  See the License for the specific language governing permissions and
206
206
  limitations under the License.
207
207
 
208
- Requires-Python: >=3.10
208
+ Requires-Python: >=3.11
209
209
  Description-Content-Type: text/markdown
210
210
  License-File: LICENSE
211
- Requires-Dist: boto3>=1.35
211
+ Requires-Dist: boto3>=1.39
212
212
  Requires-Dist: class_registry>=2.1
213
213
  Requires-Dist: fiona>=1.10
214
- Requires-Dist: fsspec[gcs,s3]>=2024.10
215
- Requires-Dist: lightning[pytorch-extra]>=2.4
216
- Requires-Dist: Pillow>=11.0
214
+ Requires-Dist: fsspec==2025.3.0
215
+ Requires-Dist: jsonargparse>=4.35.0
216
+ Requires-Dist: lightning>=2.5.1.post0
217
+ Requires-Dist: Pillow>=11.3
217
218
  Requires-Dist: pyproj>=3.7
218
219
  Requires-Dist: python-dateutil>=2.9
219
220
  Requires-Dist: pytimeparse>=1.1
220
221
  Requires-Dist: rasterio>=1.4
221
- Requires-Dist: shapely>=2.0
222
- Requires-Dist: torch>=2.5
223
- Requires-Dist: torchvision>=0.20
224
- Requires-Dist: tqdm>=4.66
225
- Requires-Dist: universal_pathlib>=0.2.5
222
+ Requires-Dist: shapely>=2.1
223
+ Requires-Dist: torch>=2.7.0
224
+ Requires-Dist: torchvision>=0.22.0
225
+ Requires-Dist: tqdm>=4.67
226
+ Requires-Dist: universal_pathlib>=0.2.6
226
227
  Provides-Extra: extra
227
- Requires-Dist: accelerate>=1.0; extra == "extra"
228
- Requires-Dist: earthengine-api>=0.1; extra == "extra"
228
+ Requires-Dist: accelerate>=1.10; extra == "extra"
229
+ Requires-Dist: cdsapi>=0.7.6; extra == "extra"
230
+ Requires-Dist: earthdaily[platform]>=1.0.0; extra == "extra"
231
+ Requires-Dist: earthengine-api>=1.6.3; extra == "extra"
229
232
  Requires-Dist: einops>=0.8; extra == "extra"
230
- Requires-Dist: gcsfs>=2024.10; extra == "extra"
231
- Requires-Dist: google-cloud-bigquery>=2.18; extra == "extra"
233
+ Requires-Dist: gcsfs==2025.3.0; extra == "extra"
234
+ Requires-Dist: google-cloud-bigquery>=3.35; extra == "extra"
232
235
  Requires-Dist: google-cloud-storage>=2.18; extra == "extra"
233
- Requires-Dist: interrogate>=1.7; extra == "extra"
234
- Requires-Dist: osmium>=3.7; extra == "extra"
235
- Requires-Dist: planet>=2.10; extra == "extra"
236
+ Requires-Dist: netCDF4>=1.7.2; extra == "extra"
237
+ Requires-Dist: osmium>=4.0.2; extra == "extra"
238
+ Requires-Dist: planet>=3.1; extra == "extra"
239
+ Requires-Dist: planetary_computer>=1.0; extra == "extra"
236
240
  Requires-Dist: pycocotools>=2.0; extra == "extra"
237
- Requires-Dist: rtree>=1.2; extra == "extra"
238
- Requires-Dist: s3fs>=2024.10.0; extra == "extra"
241
+ Requires-Dist: pystac_client>=0.9; extra == "extra"
242
+ Requires-Dist: rtree>=1.4; extra == "extra"
243
+ Requires-Dist: s3fs==2025.3.0; extra == "extra"
239
244
  Requires-Dist: satlaspretrain_models>=0.3; extra == "extra"
240
- Requires-Dist: scipy>=1.13; extra == "extra"
241
- Requires-Dist: transformers>=4.45; extra == "extra"
242
- Requires-Dist: wandb>=0.17; extra == "extra"
245
+ Requires-Dist: scipy>=1.16; extra == "extra"
246
+ Requires-Dist: terratorch>=1.0.2; extra == "extra"
247
+ Requires-Dist: transformers>=4.55; extra == "extra"
248
+ Requires-Dist: wandb>=0.21; extra == "extra"
249
+ Provides-Extra: dev
250
+ Requires-Dist: interrogate>=1.7.0; extra == "dev"
251
+ Requires-Dist: mypy<2,>=1.17.1; extra == "dev"
252
+ Requires-Dist: pre-commit>=4.3.0; extra == "dev"
253
+ Requires-Dist: pytest>=8.0; extra == "dev"
254
+ Requires-Dist: pytest_httpserver; extra == "dev"
255
+ Requires-Dist: ruff>=0.12.9; extra == "dev"
256
+ Requires-Dist: pytest-dotenv; extra == "dev"
257
+ Requires-Dist: pytest-xdist; extra == "dev"
258
+ Dynamic: license-file
243
259
 
244
260
  Overview
245
261
  --------
@@ -259,10 +275,11 @@ rslearn helps with:
259
275
 
260
276
 
261
277
  Quick links:
262
- - [CoreConcepts](CoreConcepts.md) summarizes key concepts in rslearn, including
278
+ - [CoreConcepts](docs/CoreConcepts.md) summarizes key concepts in rslearn, including
263
279
  datasets, windows, layers, and data sources.
264
- - [Examples](Examples.md) contains more examples, including customizing different
280
+ - [Examples](docs/Examples.md) contains more examples, including customizing different
265
281
  stages of rslearn with additional code.
282
+ - [DatasetConfig](docs/DatasetConfig.md) documents the dataset configuration file.
266
283
 
267
284
 
268
285
  Setup
@@ -270,9 +287,33 @@ Setup
270
287
 
271
288
  rslearn requires Python 3.10+ (Python 3.12 is recommended).
272
289
 
273
- git clone https://github.com/allenai/rslearn.git
274
- cd rslearn
275
- pip install .[extra]
290
+ ```
291
+ git clone https://github.com/allenai/rslearn.git
292
+ cd rslearn
293
+ pip install .[extra]
294
+ ```
295
+
296
+
297
+ Supported Data Sources
298
+ ----------------------
299
+
300
+ rslearn supports ingesting raster and vector data from the following data sources. Even
301
+ if you don't plan to train models within rslearn, you can still use it to easily
302
+ download, crop, and re-project data based on spatiotemporal rectangles (windows) that
303
+ you define. See [Examples](docs/Examples.md) and [DatasetConfig](docs/DatasetConfig.md)
304
+ for how to setup these data sources.
305
+
306
+ - Sentinel-1
307
+ - Sentinel-2 L1C and L2A
308
+ - Landsat 8/9 OLI-TIRS
309
+ - National Agriculture Imagery Program
310
+ - OpenStreetMap
311
+ - Xyz (Slippy) Tiles (e.g., Mapbox tiles)
312
+ - Planet Labs (PlanetScope, SkySat)
313
+ - ESA WorldCover 2021
314
+
315
+ rslearn can also be used to easily mosaic, crop, and re-project any sets of local
316
+ raster and vector files you may have.
276
317
 
277
318
 
278
319
  Example Usage
@@ -286,28 +327,25 @@ Let's start by defining a region of interest and obtaining Sentinel-2 images. Cr
286
327
  directory `/path/to/dataset` and corresponding configuration file at
287
328
  `/path/to/dataset/config.json` as follows:
288
329
 
289
- {
290
- "layers": {
291
- "sentinel2": {
292
- "type": "raster",
293
- "band_sets": [{
294
- "dtype": "uint8",
295
- "bands": ["R", "G", "B"]
296
- }],
297
- "data_source": {
298
- "name": "rslearn.data_sources.gcp_public_data.Sentinel2",
299
- "index_cache_dir": "cache/sentinel2/",
300
- "max_time_delta": "1d",
301
- "sort_by": "cloud_cover",
302
- "use_rtree_index": false
303
- }
330
+ ```json
331
+ {
332
+ "layers": {
333
+ "sentinel2": {
334
+ "type": "raster",
335
+ "band_sets": [{
336
+ "dtype": "uint8",
337
+ "bands": ["R", "G", "B"]
338
+ }],
339
+ "data_source": {
340
+ "name": "rslearn.data_sources.gcp_public_data.Sentinel2",
341
+ "index_cache_dir": "cache/sentinel2/",
342
+ "sort_by": "cloud_cover",
343
+ "use_rtree_index": false
304
344
  }
305
- },
306
- "tile_store": {
307
- "name": "file",
308
- "root_dir": "tiles"
309
345
  }
310
346
  }
347
+ }
348
+ ```
311
349
 
312
350
  Here, we have initialized an empty dataset and defined a raster layer called
313
351
  `sentinel2`. Because it specifies a data source, it will be populated automatically. In
@@ -319,8 +357,10 @@ choosing the scenes with minimal cloud cover.
319
357
  Next, let's create our spatiotemporal windows. These will correspond to training
320
358
  examples.
321
359
 
322
- export DATASET_PATH=/path/to/dataset
323
- rslearn dataset add_windows --root $DATASET_PATH --group default --utm --resolution 10 --grid_size 128 --src_crs EPSG:4326 --box=-122.6901,47.2079,-121.4955,47.9403 --start 2024-06-01T00:00:00+00:00 --end 2024-08-01T00:00:00+00:00 --name seattle
360
+ ```
361
+ export DATASET_PATH=/path/to/dataset
362
+ rslearn dataset add_windows --root $DATASET_PATH --group default --utm --resolution 10 --grid_size 128 --src_crs EPSG:4326 --box=-122.6901,47.2079,-121.4955,47.9403 --start 2024-06-01T00:00:00+00:00 --end 2024-08-01T00:00:00+00:00 --name seattle
363
+ ```
324
364
 
325
365
  This creates windows along a 128x128 grid in the specified projection (i.e.,
326
366
  appropriate UTM zone for the location with 10 m/pixel resolution) covering the
@@ -332,9 +372,11 @@ We can now obtain the Sentinel-2 images by running prepare, ingest, and material
332
372
  * Ingest: retrieve those items. This step populates the `tiles` directory within the dataset.
333
373
  * Materialize: crop/mosaic the items to align with the windows. This populates the `layers` folder in each window directory.
334
374
 
335
- rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
336
- rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
337
- rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
375
+ ```
376
+ rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
377
+ rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
378
+ rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
379
+ ```
338
380
 
339
381
  For ingestion, you may need to reduce the number of workers depending on the available
340
382
  memory on your system.
@@ -342,32 +384,36 @@ memory on your system.
342
384
  You should now be able to open the GeoTIFF images. Let's find the window that
343
385
  corresponds to downtown Seattle:
344
386
 
345
- import shapely
346
- from rslearn.const import WGS84_PROJECTION
347
- from rslearn.dataset import Dataset
348
- from rslearn.utils import Projection, STGeometry
349
- from upath import UPath
350
-
351
- # Define longitude and latitude for downtown Seattle.
352
- downtown_seattle = shapely.Point(-122.333, 47.606)
353
-
354
- # Iterate over the windows and find the closest one.
355
- dataset = Dataset(path=UPath("/path/to/dataset"))
356
- best_window_name = None
357
- best_distance = None
358
- for window in dataset.load_windows(workers=32):
359
- shp = window.get_geometry().to_projection(WGS84_PROJECTION).shp
360
- distance = shp.distance(downtown_seattle)
361
- if best_distance is None or distance < best_distance:
362
- best_window_name = window.name
363
- best_distance = distance
364
-
365
- print(best_window_name)
387
+ ```python
388
+ import shapely
389
+ from rslearn.const import WGS84_PROJECTION
390
+ from rslearn.dataset import Dataset
391
+ from rslearn.utils import Projection, STGeometry
392
+ from upath import UPath
393
+
394
+ # Define longitude and latitude for downtown Seattle.
395
+ downtown_seattle = shapely.Point(-122.333, 47.606)
396
+
397
+ # Iterate over the windows and find the closest one.
398
+ dataset = Dataset(path=UPath("/path/to/dataset"))
399
+ best_window_name = None
400
+ best_distance = None
401
+ for window in dataset.load_windows(workers=32):
402
+ shp = window.get_geometry().to_projection(WGS84_PROJECTION).shp
403
+ distance = shp.distance(downtown_seattle)
404
+ if best_distance is None or distance < best_distance:
405
+ best_window_name = window.name
406
+ best_distance = distance
407
+
408
+ print(best_window_name)
409
+ ```
366
410
 
367
411
  It should be `seattle_54912_-527360`, so let's open it in qgis (or your favorite GIS
368
412
  software):
369
413
 
370
- qgis $DATASET_PATH/windows/default/seattle_54912_-527360/layers/sentinel2/R_G_B/geotiff.tif
414
+ ```
415
+ qgis $DATASET_PATH/windows/default/seattle_54912_-527360/layers/sentinel2/R_G_B/geotiff.tif
416
+ ```
371
417
 
372
418
 
373
419
  ### Adding Land Cover Labels
@@ -377,152 +423,164 @@ the ESA WorldCover land cover map as labels.
377
423
 
378
424
  Start by downloading the WorldCover data from https://worldcover2021.esa.int
379
425
 
380
- wget https://worldcover2021.esa.int/data/archive/ESA_WorldCover_10m_2021_v200_60deg_macrotile_N30W180.zip
381
- mkdir world_cover_tifs
382
- unzip ESA_WorldCover_10m_2021_v200_60deg_macrotile_N30W180.zip -d world_cover_tifs/
426
+ ```
427
+ wget https://worldcover2021.esa.int/data/archive/ESA_WorldCover_10m_2021_v200_60deg_macrotile_N30W180.zip
428
+ mkdir world_cover_tifs
429
+ unzip ESA_WorldCover_10m_2021_v200_60deg_macrotile_N30W180.zip -d world_cover_tifs/
430
+ ```
383
431
 
384
432
  It would require some work to write a script to re-project and crop these GeoTIFFs so
385
433
  that they align with the windows we have previously defined (and the Sentinel-2 images
386
434
  we have already ingested). We can use the LocalFiles data source to have rslearn
387
435
  automate this process. Update the dataset `config.json` with a new layer:
388
436
 
389
- "layers": {
390
- "sentinel2": {
391
- ...
392
- },
393
- "worldcover": {
394
- "type": "raster",
395
- "band_sets": [{
396
- "dtype": "uint8",
397
- "bands": ["B1"]
398
- }],
399
- "resampling_method": "nearest",
400
- "data_source": {
401
- "name": "rslearn.data_sources.local_files.LocalFiles",
402
- "src_dir": "file:///path/to/world_cover_tifs/"
403
- }
404
- }
437
+ ```json
438
+ "layers": {
439
+ "sentinel2": {
440
+ ...
405
441
  },
406
- ...
442
+ "worldcover": {
443
+ "type": "raster",
444
+ "band_sets": [{
445
+ "dtype": "uint8",
446
+ "bands": ["B1"]
447
+ }],
448
+ "resampling_method": "nearest",
449
+ "data_source": {
450
+ "name": "rslearn.data_sources.local_files.LocalFiles",
451
+ "src_dir": "file:///path/to/world_cover_tifs/"
452
+ }
453
+ }
454
+ },
455
+ ...
456
+ ```
407
457
 
408
458
  Repeat the materialize process so we populate the data for this new layer:
409
459
 
410
- rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
411
- rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
412
- rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
460
+ ```
461
+ rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
462
+ rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
463
+ rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
464
+ ```
413
465
 
414
466
  We can visualize both the GeoTIFFs together in qgis:
415
467
 
416
- qgis $DATASET_PATH/windows/default/seattle_54912_-527360/layers/*/*/geotiff.tif
468
+ ```
469
+ qgis $DATASET_PATH/windows/default/seattle_54912_-527360/layers/*/*/geotiff.tif
470
+ ```
417
471
 
418
472
 
419
473
  ### Training a Model
420
474
 
421
475
  Create a model configuration file `land_cover_model.yaml`:
422
476
 
477
+ ```yaml
478
+ model:
479
+ class_path: rslearn.train.lightning_module.RslearnLightningModule
480
+ init_args:
481
+ # This part defines the model architecture.
482
+ # Essentially we apply the SatlasPretrain Sentinel-2 backbone with a UNet decoder
483
+ # that terminates at a segmentation prediction head.
484
+ # The backbone outputs four feature maps at different scales, and the UNet uses
485
+ # these to compute a feature map at the input scale.
486
+ # Finally the segmentation head applies per-pixel softmax to compute the land
487
+ # cover class.
423
488
  model:
424
- class_path: rslearn.train.lightning_module.RslearnLightningModule
489
+ class_path: rslearn.models.singletask.SingleTaskModel
425
490
  init_args:
426
- # This part defines the model architecture.
427
- # Essentially we apply the SatlasPretrain Sentinel-2 backbone with a UNet decoder
428
- # that terminates at a segmentation prediction head.
429
- # The backbone outputs four feature maps at different scales, and the UNet uses
430
- # these to compute a feature map at the input scale.
431
- # Finally the segmentation head applies per-pixel softmax to compute the land
432
- # cover class.
433
- model:
434
- class_path: rslearn.models.singletask.SingleTaskModel
435
- init_args:
436
- encoder:
437
- - class_path: rslearn.models.satlaspretrain.SatlasPretrain
438
- init_args:
439
- model_identifier: "Sentinel2_SwinB_SI_RGB"
440
- decoder:
441
- - class_path: rslearn.models.unet.UNetDecoder
442
- init_args:
443
- in_channels: [[4, 128], [8, 256], [16, 512], [32, 1024]]
444
- # We use 101 classes because the WorldCover classes are 10, 20, 30, 40
445
- # 50, 60, 70, 80, 90, 95, 100.
446
- # We could process the GeoTIFFs to collapse them to 0-10 (the 11 actual
447
- # classes) but the model will quickly learn that the intermediate
448
- # values are never used.
449
- out_channels: 101
450
- conv_layers_per_resolution: 2
451
- - class_path: rslearn.train.tasks.segmentation.SegmentationHead
452
- # Remaining parameters in RslearnLightningModule define different aspects of the
453
- # training process like initial learning rate.
454
- lr: 0.0001
455
- data:
456
- class_path: rslearn.train.data_module.RslearnDataModule
491
+ encoder:
492
+ - class_path: rslearn.models.satlaspretrain.SatlasPretrain
493
+ init_args:
494
+ model_identifier: "Sentinel2_SwinB_SI_RGB"
495
+ decoder:
496
+ - class_path: rslearn.models.unet.UNetDecoder
497
+ init_args:
498
+ in_channels: [[4, 128], [8, 256], [16, 512], [32, 1024]]
499
+ # We use 101 classes because the WorldCover classes are 10, 20, 30, 40
500
+ # 50, 60, 70, 80, 90, 95, 100.
501
+ # We could process the GeoTIFFs to collapse them to 0-10 (the 11 actual
502
+ # classes) but the model will quickly learn that the intermediate
503
+ # values are never used.
504
+ out_channels: 101
505
+ conv_layers_per_resolution: 2
506
+ - class_path: rslearn.train.tasks.segmentation.SegmentationHead
507
+ # Remaining parameters in RslearnLightningModule define different aspects of the
508
+ # training process like initial learning rate.
509
+ lr: 0.0001
510
+ data:
511
+ class_path: rslearn.train.data_module.RslearnDataModule
512
+ init_args:
513
+ # Replace this with the dataset path.
514
+ path: /path/to/dataset/
515
+ # This defines the layers that should be read for each window.
516
+ # The key ("image" / "targets") is what the data will be called in the model,
517
+ # while the layers option specifies which layers will be read.
518
+ inputs:
519
+ image:
520
+ data_type: "raster"
521
+ layers: ["sentinel2"]
522
+ bands: ["R", "G", "B"]
523
+ passthrough: true
524
+ targets:
525
+ data_type: "raster"
526
+ layers: ["worldcover"]
527
+ bands: ["B1"]
528
+ is_target: true
529
+ task:
530
+ # Train for semantic segmentation.
531
+ # The remap option is only used when visualizing outputs during testing.
532
+ class_path: rslearn.train.tasks.segmentation.SegmentationTask
457
533
  init_args:
458
- # Replace this with the dataset path.
459
- path: /path/to/dataset/
460
- # This defines the layers that should be read for each window.
461
- # The key ("image" / "targets") is what the data will be called in the model,
462
- # while the layers option specifies which layers will be read.
463
- inputs:
464
- image:
465
- data_type: "raster"
466
- layers: ["sentinel2"]
467
- bands: ["R", "G", "B"]
468
- passthrough: true
469
- targets:
470
- data_type: "raster"
471
- layers: ["worldcover"]
472
- bands: ["B1"]
473
- is_target: true
474
- task:
475
- # Train for semantic segmentation.
476
- # The remap option is only used when visualizing outputs during testing.
477
- class_path: rslearn.train.tasks.segmentation.SegmentationTask
534
+ num_classes: 101
535
+ remap_values: [[0, 1], [0, 255]]
536
+ batch_size: 8
537
+ num_workers: 32
538
+ # These define different options for different phases/splits, like training,
539
+ # validation, and testing.
540
+ # Here we use the same transform across splits except training where we add a
541
+ # flipping augmentation.
542
+ # For now we are using the same windows for training and validation.
543
+ default_config:
544
+ transforms:
545
+ - class_path: rslearn.train.transforms.normalize.Normalize
478
546
  init_args:
479
- num_classes: 101
480
- remap_values: [[0, 1], [0, 255]]
481
- batch_size: 8
482
- num_workers: 32
483
- # These define different options for different phases/splits, like training,
484
- # validation, and testing.
485
- # Here we use the same transform across splits except training where we add a
486
- # flipping augmentation.
487
- # For now we are using the same windows for training and validation.
488
- default_config:
489
- transforms:
490
- - class_path: rslearn.train.transforms.normalize.Normalize
491
- init_args:
492
- mean: 0
493
- std: 255
494
- train_config:
495
- transforms:
496
- - class_path: rslearn.train.transforms.normalize.Normalize
497
- init_args:
498
- mean: 0
499
- std: 255
500
- - class_path: rslearn.train.transforms.flip.Flip
501
- init_args:
502
- image_selectors: ["image", "target/classes", "target/valid"]
503
- groups: ["default"]
504
- val_config:
505
- groups: ["default"]
506
- test_config:
507
- groups: ["default"]
508
- predict_config:
509
- groups: ["predict"]
510
- load_all_patches: true
511
- skip_targets: true
512
- patch_size: 512
513
- trainer:
514
- max_epochs: 10
515
- callbacks:
516
- - class_path: lightning.pytorch.callbacks.ModelCheckpoint
547
+ mean: 0
548
+ std: 255
549
+ train_config:
550
+ transforms:
551
+ - class_path: rslearn.train.transforms.normalize.Normalize
552
+ init_args:
553
+ mean: 0
554
+ std: 255
555
+ - class_path: rslearn.train.transforms.flip.Flip
517
556
  init_args:
518
- save_top_k: 1
519
- save_last: true
520
- monitor: val_accuracy
521
- mode: max
557
+ image_selectors: ["image", "target/classes", "target/valid"]
558
+ groups: ["default"]
559
+ val_config:
560
+ groups: ["default"]
561
+ test_config:
562
+ groups: ["default"]
563
+ predict_config:
564
+ groups: ["predict"]
565
+ load_all_patches: true
566
+ skip_targets: true
567
+ patch_size: 512
568
+ trainer:
569
+ max_epochs: 10
570
+ callbacks:
571
+ - class_path: lightning.pytorch.callbacks.ModelCheckpoint
572
+ init_args:
573
+ save_top_k: 1
574
+ save_last: true
575
+ monitor: val_accuracy
576
+ mode: max
577
+ ```
522
578
 
523
579
  Now we can train the model:
524
580
 
525
- rslearn model fit --config land_cover_model.yaml
581
+ ```
582
+ rslearn model fit --config land_cover_model.yaml
583
+ ```
526
584
 
527
585
 
528
586
  ### Apply the Model
@@ -533,22 +591,26 @@ windows along a grid, we just create one big window. This is because we are just
533
591
  to run the prediction over the whole window rather than use different windows as
534
592
  different training examples.
535
593
 
536
- rslearn dataset add_windows --root $DATASET_PATH --group predict --utm --resolution 10 --src_crs EPSG:4326 --box=-122.712,45.477,-122.621,45.549 --start 2024-06-01T00:00:00+00:00 --end 2024-08-01T00:00:00+00:00 --name portland
537
- rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
538
- rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
539
- rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
594
+ ```
595
+ rslearn dataset add_windows --root $DATASET_PATH --group predict --utm --resolution 10 --src_crs EPSG:4326 --box=-122.712,45.477,-122.621,45.549 --start 2024-06-01T00:00:00+00:00 --end 2024-08-01T00:00:00+00:00 --name portland
596
+ rslearn dataset prepare --root $DATASET_PATH --workers 32 --batch-size 8
597
+ rslearn dataset ingest --root $DATASET_PATH --workers 32 --no-use-initial-job --jobs-per-process 1
598
+ rslearn dataset materialize --root $DATASET_PATH --workers 32 --no-use-initial-job
599
+ ```
540
600
 
541
601
  We also need to add an RslearnPredictionWriter to the trainer callbacks in the model
542
602
  configuration file, as it will handle writing the outputs from the model to a GeoTIFF.
543
603
 
544
- trainer:
545
- callbacks:
546
- - class_path: lightning.pytorch.callbacks.ModelCheckpoint
547
- ...
548
- - class_path: rslearn.train.prediction_writer.RslearnWriter
549
- init_args:
550
- path: /path/to/dataset/
551
- output_layer: output
604
+ ```yaml
605
+ trainer:
606
+ callbacks:
607
+ - class_path: lightning.pytorch.callbacks.ModelCheckpoint
608
+ ...
609
+ - class_path: rslearn.train.prediction_writer.RslearnWriter
610
+ init_args:
611
+ path: /path/to/dataset/
612
+ output_layer: output
613
+ ```
552
614
 
553
615
  Because of our `predict_config`, when we run `model predict` it will apply the model on
554
616
  windows in the "predict" group, which is where we added the Portland window.
@@ -556,39 +618,46 @@ windows in the "predict" group, which is where we added the Portland window.
556
618
  And it will be written in a new output_layer called "output". But we have to update the
557
619
  dataset configuration so it specifies the layer:
558
620
 
559
-
560
- "layers": {
561
- "sentinel2": {
562
- ...
563
- },
564
- "worldcover": {
565
- ...
566
- },
567
- "output": {
568
- "type": "raster",
569
- "band_sets": [{
570
- "dtype": "uint8",
571
- "bands": ["output"]
572
- }]
573
- }
621
+ ```json
622
+ "layers": {
623
+ "sentinel2": {
624
+ ...
625
+ },
626
+ "worldcover": {
627
+ ...
574
628
  },
629
+ "output": {
630
+ "type": "raster",
631
+ "band_sets": [{
632
+ "dtype": "uint8",
633
+ "bands": ["output"]
634
+ }]
635
+ }
636
+ },
637
+ ```
575
638
 
576
639
  Now we can apply the model:
577
640
 
578
- # Find model checkpoint in lightning_logs dir.
579
- ls lightning_logs/*/checkpoints/last.ckpt
580
- rslearn model predict --config land_cover_model.yaml --ckpt_path lightning_logs/version_0/checkpoints/last.ckpt
641
+ ```
642
+ # Find model checkpoint in lightning_logs dir.
643
+ ls lightning_logs/*/checkpoints/last.ckpt
644
+ rslearn model predict --config land_cover_model.yaml --ckpt_path lightning_logs/version_0/checkpoints/last.ckpt
645
+ ```
581
646
 
582
647
  And visualize the Sentinel-2 image and output in qgis:
583
648
 
584
- qgis $DATASET_PATH/windows/predict/portland/layers/*/*/geotiff.tif
649
+ ```
650
+ qgis $DATASET_PATH/windows/predict/portland/layers/*/*/geotiff.tif
651
+ ```
585
652
 
586
653
 
587
654
  ### Defining Train and Validation Splits
588
655
 
589
656
  We can visualize the logged metrics using Tensorboard:
590
657
 
591
- tensorboard --logdir=lightning_logs/
658
+ ```
659
+ tensorboard --logdir=lightning_logs/
660
+ ```
592
661
 
593
662
  However, because our training and validation data are identical, the validation metrics
594
663
  are not meaningful.
@@ -602,57 +671,61 @@ We will use the second approach. The script below sets a "split" key in the opti
602
671
  dict (which is stored in each window's `metadata.json` file) to "train" or "val"
603
672
  based on the SHA-256 hash of the window name.
604
673
 
605
- import hashlib
606
- import tqdm
607
- from rslearn.dataset import Dataset, Window
608
- from upath import UPath
609
-
610
- ds_path = UPath("/path/to/dataset/")
611
- dataset = Dataset(ds_path)
612
- windows = dataset.load_windows(show_progress=True, workers=32)
613
- for window in tqdm.tqdm(windows):
614
- if hashlib.sha256(window.name.encode()).hexdigest()[0] in ["0", "1"]:
615
- split = "val"
616
- else:
617
- split = "train"
618
- if "split" in window.options and window.options["split"] == split:
619
- continue
620
- window.options["split"] = split
621
- window.save()
674
+ ```python
675
+ import hashlib
676
+ import tqdm
677
+ from rslearn.dataset import Dataset, Window
678
+ from upath import UPath
679
+
680
+ ds_path = UPath("/path/to/dataset/")
681
+ dataset = Dataset(ds_path)
682
+ windows = dataset.load_windows(show_progress=True, workers=32)
683
+ for window in tqdm.tqdm(windows):
684
+ if hashlib.sha256(window.name.encode()).hexdigest()[0] in ["0", "1"]:
685
+ split = "val"
686
+ else:
687
+ split = "train"
688
+ if "split" in window.options and window.options["split"] == split:
689
+ continue
690
+ window.options["split"] = split
691
+ window.save()
692
+ ```
622
693
 
623
694
  Now we can update the model configuration file to use these splits:
624
695
 
625
- default_config:
626
- transforms:
627
- - class_path: rslearn.train.transforms.normalize.Normalize
628
- init_args:
629
- mean: 0
630
- std: 255
631
- train_config:
632
- transforms:
633
- - class_path: rslearn.train.transforms.normalize.Normalize
634
- init_args:
635
- mean: 0
636
- std: 255
637
- - class_path: rslearn.train.transforms.flip.Flip
638
- init_args:
639
- image_selectors: ["image", "target/classes", "target/valid"]
640
- groups: ["default"]
641
- tags:
642
- split: train
643
- val_config:
644
- groups: ["default"]
645
- tags:
646
- split: val
647
- test_config:
648
- groups: ["default"]
649
- tags:
650
- split: val
651
- predict_config:
652
- groups: ["predict"]
653
- load_all_patches: true
654
- skip_targets: true
655
- patch_size: 512
696
+ ```yaml
697
+ default_config:
698
+ transforms:
699
+ - class_path: rslearn.train.transforms.normalize.Normalize
700
+ init_args:
701
+ mean: 0
702
+ std: 255
703
+ train_config:
704
+ transforms:
705
+ - class_path: rslearn.train.transforms.normalize.Normalize
706
+ init_args:
707
+ mean: 0
708
+ std: 255
709
+ - class_path: rslearn.train.transforms.flip.Flip
710
+ init_args:
711
+ image_selectors: ["image", "target/classes", "target/valid"]
712
+ groups: ["default"]
713
+ tags:
714
+ split: train
715
+ val_config:
716
+ groups: ["default"]
717
+ tags:
718
+ split: val
719
+ test_config:
720
+ groups: ["default"]
721
+ tags:
722
+ split: val
723
+ predict_config:
724
+ groups: ["predict"]
725
+ load_all_patches: true
726
+ skip_targets: true
727
+ patch_size: 512
728
+ ```
656
729
 
657
730
  The `tags` option that we are adding here tells rslearn to only load windows with a
658
731
  matching key and value in the window options.
@@ -660,13 +733,17 @@ matching key and value in the window options.
660
733
  Previously when we run `model fit`, it should show the same number of windows for
661
734
  training and validation:
662
735
 
663
- got 4752 examples in split train
664
- got 4752 examples in split val
736
+ ```
737
+ got 4752 examples in split train
738
+ got 4752 examples in split val
739
+ ```
665
740
 
666
741
  With the updates, it should show different numbers like this:
667
742
 
668
- got 4167 examples in split train
669
- got 585 examples in split val
743
+ ```
744
+ got 4167 examples in split train
745
+ got 585 examples in split val
746
+ ```
670
747
 
671
748
 
672
749
  ### Visualizing with `model test`