Bring Your Own COG
Sentinel Hub allows you to access your own data stored in your S3 bucket with the powerful Sentinel Hub API. Since data remains on your bucket, you keep full control over it. This functionality requires no replication of data and allows you to exercise the full power of the Sentinel Hub service including Custom algorithms. More information here!
The Sentinel Hub Dashboard has a very user-friendly “Bring your own COG” tab. If you are not going to be creating collections, adding/updating collection tiles, etc. daily, the Dashboard tool is your friend. For the rest, this tutorial is a simple walk-through on creating, updating, listing, and deleting your BYOC collections through Python using sentinelhub-py
.
Some general and BYOC related functionality imports:
[1]:
# Configure plots for inline use in Jupyter Notebook
%matplotlib inline
import datetime as dt
# Utilities
import boto3
import dateutil
import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
# Sentinel Hub
from sentinelhub import (
CRS,
BBox,
ByocCollection,
ByocCollectionAdditionalData,
ByocCollectionBand,
ByocTile,
DataCollection,
DownloadFailedException,
MimeType,
SentinelHubBYOC,
SentinelHubRequest,
SHConfig,
bbox_to_dimensions,
)
Prerequisites
BYOC API requires Sentinel Hub account. Please see configuration instructions how to set up your configuration.
[2]:
# Insert your credentials here in case you don't already have them in the config.toml file:
SH_CLIENT_ID = ""
SH_CLIENT_SECRET = ""
AWS_ACCESS_KEY_ID = ""
AWS_SECRET_ACCESS_KEY = ""
config = SHConfig()
if SH_CLIENT_ID and SH_CLIENT_SECRET:
config.sh_client_id = SH_CLIENT_ID
config.sh_client_secret = SH_CLIENT_SECRET
if not config.sh_client_id or not config.sh_client_secret:
print("Warning! To use Sentinel Hub BYOC API, please provide the credentials (client ID and client secret).")
if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
config.aws_access_key_id = AWS_ACCESS_KEY_ID
config.aws_secret_access_key = AWS_SECRET_ACCESS_KEY
BYOC collections
SentinelHubBYOC
class holds the methods for interacting with Sentinel Hub services. Let’s initialize it with our config
:
[3]:
# Initialize SentinelHubBYOC class
byoc = SentinelHubBYOC(config=config)
Create new collection
The easiest way to create a collection is to use its dataclass:
[4]:
new_collection = ByocCollection(name="new collection", s3_bucket="my-s3-bucket")
The new collection
is accessible on my-s3-bucket
s3 bucket (please see how to configure your bucket for Sentinel Hub service here).
The call to create the collection on Sentinel Hub, will return newly created collection, which will get its own collection id:
[5]:
created_collection = byoc.create_collection(new_collection)
[6]:
print("name:", created_collection["name"])
print("id:", created_collection["id"])
name: new collection
id: b676c6c4-1133-4328-afa7-e9becc5ba590
Get a list of your collections
Now we have created a data collection named new collection
, we can retrieve it with the following code.
[7]:
my_collection = byoc.get_collection(created_collection["id"])
Let’s have a look at the the collection we just created.
[8]:
print(f"name: {my_collection['name']}")
print(f"id: {my_collection['id']}")
name: new collection
id: b676c6c4-1133-4328-afa7-e9becc5ba590
In cases where you have a large amount of collections and you would only like to load collection info for a few collections, the following code would be a good option for you:
[9]:
collections_iterator = byoc.iter_collections()
[10]:
my_collection_using_next = next(collections_iterator)
print("name:", my_collection_using_next["name"])
print("id:", my_collection_using_next["id"])
name: new collection
id: b676c6c4-1133-4328-afa7-e9becc5ba590
Note: collections_iterator
won’t necessarily return collections in the same order they were created. If you already have collections, the output above could show a collection other than the one we just created.
If you prefer to work with dataclasses, you can also run the following code:
my_collection = ByocCollection.from_dict(next(collections_iterator))
One can of course retrieve all of them in one go like so:
[11]:
my_collections = list(collections_iterator)
for collection in my_collections:
print((collection["name"], collection["id"]))
('new collection', 'b676c6c4-1133-4328-afa7-e9becc5ba590')
A useful way for managing your collections is pandas.DataFrame
you can create like so:
[12]:
my_collections_df = pd.DataFrame(data=list(byoc.iter_collections()))
my_collections_df[["id", "name", "created"]].head()
[12]:
id | name | created | |
---|---|---|---|
0 | b676c6c4-1133-4328-afa7-e9becc5ba590 | new collection | 2022-07-22T08:23:00.480642Z |
Update existing collection
Anything you can do on Dashboard, Bring your own COG tab, you can do programmatically as well. Below we’re going to rename the new collection
collection to renamed new collection
:
[13]:
my_collection["name"] = "renamed new collection"
When using next()
, run the following code:
my_collection['name'] = 'renamed new collection'
When using dataclass, run the following code:
my_collection.name = 'renamed new collection'
When using list
, run the following code:
collection_to_be_updated = [
collection for collection in my_collections
if collection['id'] == my_collection['id']
][0]
collection_to_be_updated['name'] = 'renamed new collection'
Note: While you can change other fields as well, s3_bucket
cannot be changed, and the bitDepth
of bands in the collection is something that is pertinent to the COGs themselves and populated during the ingestion.
To update the collection, call:
[14]:
byoc.update_collection(my_collection)
[14]:
''
Now we can see that the new collection
collection has been renamed as renamed new collection
.
[15]:
get_renamed_collection = byoc.get_collection(created_collection["id"])
print("name:", get_renamed_collection["name"])
print("id:", get_renamed_collection["id"])
name: renamed new collection
id: b676c6c4-1133-4328-afa7-e9becc5ba590
Delete collection
If you are the owner of the collection, you can also delete it.
Warning:
Beware! Deleting the collection will also delete all its tiles!
[16]:
byoc.delete_collection(my_collection)
[16]:
''
The collection can also be deleted via passing its id to byoc.delete_collection()
as shown below:
byoc.delete_collection(my_collection['id'])
Trying to access this collection now will fail.
[ ]:
try:
deleted_collection = byoc.get_collection(my_collection["id"])
except DownloadFailedException as e:
print(e)
BYOC tiles (COGs in the collection)
Your data needs to be organized into collections of tiles. Each tile needs to contain a set of bands and (optionally) an acquisition date and time. Tiles with the same bands can be grouped into collections. Think of the Sentinel-2 data source as a collection of Sentinel-2 tiles.
Tiles have to be on an s3 bucket and need to be in COG format. We will not go into details about the COGification process; users can have a look at the documentation or use the BYOC tool that will take care of creating a collection and ingesting the tiles for you.
Creating a new tile (and ingesting it to collection)
When we create a new tile and add it to the collection, the ingestion process on the Sentinel Hub side will happen, checking if the tile corresponds to the COG specifications as well as if it conforms to the collection.
The simplest way to create a new tile is by using the ByocTile
dataclass, which will complain if the required fields are missing.
[18]:
new_tile = ByocTile(path="2019/11/27/28V/(BAND).tif", sensing_time=dt.datetime(2019, 11, 27))
Note:
The most important field of the tile is its
path
on an s3 bucket. For example, if your band files are stored ins3://bucket-name/folder/
, then setfolder
as the tile path. In this case, the band names will equal the file names. For example, the band B1 corresponds to the files3://bucket-name/folder/B1.tiff
. If your file names have something other than just the band name, such as a prefix, this is fine as long as the prefix is the same for all files. In this case, the path needs to include this prefix and also the band placeholder:(BAND)
. Adding the extension is optional. For example, this is what would happen if you would use the following pathfolder/tile_1_(BAND)_2019.tiff
for the following files:s3://bucket-name/folder/tile_1_B1_2019.tiff
- the file would be used, the band name would be B1s3://bucket-name/folder/tile_1_B2_2019.tiff
- the file would be used, the band name would be B2s3://bucket-name/folder/tile_2_B1_2019.tiff
- the file would not be useds3://bucket-name/folder/tile_2_B2_2019.tiff
- the file would not be usedByocTile
takessensing_time
as optional parameters, but setting thesensing_time
is highly recommended since it makes the collection “temporal” and help you search for the data with Sentinel Hub services.ingestion_start
is optional and it denotes the time when the ingestion of the COGs had started.tile_geometry
is optional as it is the bounding box of the tile and will be read from COG file.cover_geometry
is the geometry of where the data (within the bounding box) is and can be useful for optimized search as an optional parameter. For a good explanation of thecoverGeometry
please see docs.
Let’s create a new collection for these tiles.
[19]:
new_collection = ByocCollection(name="byoc-s2l2a-120m-mosaic", s3_bucket="sentinel-s2-l2a-mosaic-120")
created_collection = byoc.create_collection(new_collection)
[20]:
created_tile = byoc.create_tile(created_collection, new_tile)
The response from byoc.create_tile
has a valid id
, and its status
is set to WAITING
. Checking the tile status
after a while (by requesting this tile) will tell you if it has been INGESTED
or if the ingestion procedure FAILED
. In case of failure, additional information (with the cause of failure) will be available in the tile additional_data
.
[21]:
created_tile
[21]:
{'id': '75a7bf54-2772-44b1-b87f-3f4ec96096cf',
'created': '2023-03-16T12:56:22.205845Z',
'ingestionStart': '2023-03-16T12:56:22.205845Z',
'sensingTime': '2019-11-27T00:00:00Z',
'path': '2019/11/27/28V/(BAND).tif',
'status': 'WAITING'}
Add multiple tiles to a single collection
A data collection can for sure contain multiple tiles. It is important to know that adding multiple tiles will work only if these tiles have the same bands. Let’s add more tiles from the Sentinel-2 L2A 120m Mosaic listed on the open data registry on AWS to the collection.
We first define a function to get a list of paths for each tile:
[22]:
def list_objects_path(bucket, year_count, month_count, day_count, config):
tiles_path = []
client = boto3.client(
"s3", aws_access_key_id=config.aws_access_key_id, aws_secret_access_key=config.aws_secret_access_key
)
result = client.list_objects(Bucket=bucket, Delimiter="/")
for year in result.get("CommonPrefixes")[:year_count]:
year_result = client.list_objects(Bucket=bucket, Delimiter="/", Prefix=year.get("Prefix"))
for month in year_result.get("CommonPrefixes")[:month_count]:
month_result = client.list_objects(Bucket=bucket, Delimiter="/", Prefix=month.get("Prefix"))
for day in month_result.get("CommonPrefixes")[:day_count]:
day_result = client.list_objects(Bucket=bucket, Delimiter="/", Prefix=day.get("Prefix"))
for tile in day_result.get("CommonPrefixes"):
tiles_path.append(tile.get("Prefix"))
return tiles_path
Next we obtain a list of paths for tiles available on s3://sentinel-s2-l2a-mosaic-120/2019/1/1/
.
[23]:
tiles_path = list_objects_path(
bucket="sentinel-s2-l2a-mosaic-120", year_count=1, month_count=1, day_count=1, config=config
)
Then we can add tiles to the collection with a for
loop.
[24]:
for tile in tiles_path:
year, month, day = tile.split("/")[:3]
byoc_tile = ByocTile(path=f"{tile}(BAND).tif", sensing_time=dt.datetime(int(year), int(month), int(day)))
byoc.create_tile(created_collection, byoc_tile)
Note: The tile ingesting process could take some time, please wait a few more minutes after the cell has done running before heading to the next step.
Get tiles from your collection
After byoc.create_tile
being executed, we can request the tile from the collection where it is ingested. To request a specific tile you can pass a collection id and a tile id to the get_tile
method.
[25]:
tile = byoc.get_tile(collection=created_collection["id"], tile=created_tile["id"])
[26]:
tile
[26]:
{'id': '75a7bf54-2772-44b1-b87f-3f4ec96096cf',
'created': '2023-03-16T12:56:22.205845Z',
'ingestionStart': '2023-03-16T12:56:22.205845Z',
'sensingTime': '2019-11-27T00:00:00Z',
'coverGeometry': {'type': 'Polygon',
'crs': {'type': 'name',
'properties': {'name': 'urn:ogc:def:crs:EPSG::32628'}},
'coordinates': [[[299999.9980359557, 6899880.000729979],
[600120.0000155062, 6899880.00067825],
[600120.0000231846, 7100040.000720726],
[299999.9970628682, 7100040.000814738],
[299999.9980359557, 6899880.000729979]]]},
'tileGeometry': {'type': 'Polygon',
'crs': {'type': 'name',
'properties': {'name': 'urn:ogc:def:crs:EPSG::32628'}},
'coordinates': [[[300000.0, 7100040.0],
[600120.0, 7100040.0],
[600120.0, 6899880.0],
[300000.0, 6899880.0],
[300000.0, 7100040.0]]]},
'path': '2019/11/27/28V/(BAND).tif',
'status': 'INGESTED',
'additionalData': {'hasAddedPoints': True,
'filesMetadata': {'B02': {'headerSize': 1328,
'etag': '"838a04acfa29e10b3b0a972f9ff2f658"'},
'B03': {'headerSize': 1328, 'etag': '"bc692959f00b3186efbab64853f14bbe"'},
'B04': {'headerSize': 1328, 'etag': '"26de702b115d976797cdfd00ad6f7f10"'},
'B08': {'headerSize': 1328, 'etag': '"8c638bf4ad45d2bf606f458bdc9cc165"'},
'B11': {'headerSize': 1328, 'etag': '"1179bd6c9750c4acc998346864587fe6"'},
'B12': {'headerSize': 1328, 'etag': '"826091544f541a597651007676765a73"'},
'dataMask': {'headerSize': 1328,
'etag': '"183b03ed56250b2624322e5179db585d"'}},
'minMetersPerPixel': 120.0,
'maxMetersPerPixel': 960.0}}
You can of course retrieve all tiles into a list.
[27]:
tiles = list(byoc.iter_tiles(created_collection))
In cases where you have a large collection with a lot of tiles and you would only like to load tile info for a few tiles, the following code using next()
would be a good option for you:
tile = next(byoc.iter_tiles(created_collection))
To convert it to ByocTile dataclass using the code below:
tile = ByocTile.from_dict(next(byoc.iter_tiles(created_collection)))
Let’s take a look at the keys of the first dictionary, which contains the info of the first tile, in the returned list.
[28]:
list(tiles[0].keys())
[28]:
['id',
'created',
'ingestionStart',
'sensingTime',
'coverGeometry',
'tileGeometry',
'path',
'status',
'additionalData']
To check if there’s any tile failed to be ingested, run the code below:
[29]:
tiles_failed_to_be_ingested = [tile["path"] for tile in tiles if tile["status"] == "FAILED"]
tiles_failed_to_be_ingested
[29]:
[]
Visualize the tiles in your collection
Using ByocTile
dataclass, which will properly parse tile geometries, date-time strings, etc., one can create a geopandas.GeoDataFrame
.
Note: the geometries can be in different coordinate reference systems, so a transform to a common CRS might be needed.
[30]:
tile_iterator = byoc.iter_tiles(created_collection)
[31]:
tiles_for_visualized = []
for i in range(100):
tiles_for_visualized.append(ByocTile.from_dict(next(tile_iterator)))
tiles_gdf = gpd.GeoDataFrame(
tiles_for_visualized,
geometry=[t.cover_geometry.transform(CRS.WGS84).geometry for t in tiles_for_visualized],
crs="epsg:4326",
)
[32]:
tiles_gdf.head()
[32]:
path | status | tile_id | tile_geometry | cover_geometry | created | sensing_time | ingestion_start | additional_data | other_data | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2019/1/1/15X/(BAND).tif | INGESTED | 004d2bcf-c414-40f7-a9eb-e26d63b8e5d8 | Geometry(POLYGON ((399960 8100000, 700080 8100... | Geometry(POLYGON ((399959.99978184653 7999920.... | 2023-03-16 12:56:39.547622+00:00 | 2019-01-01 00:00:00+00:00 | 2023-03-16 12:56:39.547622+00:00 | {'hasAddedPoints': True, 'filesMetadata': {'B0... | {} | POLYGON ((-95.91354 72.07680, -87.18720 72.011... |
1 | 2019/1/1/50T/(BAND).tif | INGESTED | 01c99620-7971-4a30-b974-892803ec8c5e | Geometry(POLYGON ((199980 5300040, 800100 5300... | Geometry(POLYGON ((199979.99898144224 4399920.... | 2023-03-16 12:57:22.328714+00:00 | 2019-01-01 00:00:00+00:00 | 2023-03-16 12:57:22.328714+00:00 | {'hasAddedPoints': True, 'filesMetadata': {'B0... | {} | POLYGON ((113.50108 39.69643, 120.49985 39.696... |
2 | 2019/1/1/21H/(BAND).tif | INGESTED | 01ee0e98-1e15-40e4-ac80-25bfd29be80b | Geometry(POLYGON ((199980 6500020, 800100 6500... | Geometry(POLYGON ((199979.99898124964 5599899.... | 2023-03-16 12:56:47.542399+00:00 | 2019-01-01 00:00:00+00:00 | 2023-03-16 12:56:47.542399+00:00 | {'hasAddedPoints': True, 'filesMetadata': {'B0... | {} | POLYGON ((-60.49900 -39.69805, -53.50006 -39.6... |
3 | 2019/1/1/50V/(BAND).tif | INGESTED | 02952525-edea-432f-87f4-8740c0472877 | Geometry(POLYGON ((300000 7100040, 700080 7100... | Geometry(POLYGON ((299999.99941996665 6199920.... | 2023-03-16 12:57:22.551168+00:00 | 2019-01-01 00:00:00+00:00 | 2023-03-16 12:57:22.551168+00:00 | {'hasAddedPoints': True, 'filesMetadata': {'B0... | {} | POLYGON ((113.80060 55.90309, 120.20068 55.903... |
4 | 2019/1/1/34N/(BAND).tif | INGESTED | 03803c3d-5d36-4f9a-916f-c3f1d3d8de21 | Geometry(POLYGON ((199980 900000, 900060 90000... | Geometry(POLYGON ((199980.00000812783 -119.999... | 2023-03-16 12:57:00.186216+00:00 | 2019-01-01 00:00:00+00:00 | 2023-03-16 12:57:00.186216+00:00 | {'hasAddedPoints': True, 'filesMetadata': {'B0... | {} | POLYGON ((18.30480 -0.00108, 24.59287 -0.00108... |
[33]:
fig, ax = plt.subplots(figsize=(17, 8))
tiles_gdf.plot(ax=ax);

In the above example, the ingested tiles are 100 tiles from the Sentinel-2 L2A 120m Mosaic which contains 19869 tiles around the globe, hence the tiles are so sparse in the image above.
Updating and deleting a tile
Updating and deleting a tile follow the same logic as updating/deleting a collection.
To update a tile:
[34]:
tile["sensingTime"] = "2021-06-29T18:02:34"
byoc.update_tile(created_collection, tile)
[34]:
''
After updating we can see that the sensingTime
has been changed.
[35]:
byoc.get_tile(collection=created_collection["id"], tile=created_tile["id"])["sensingTime"]
[35]:
'2021-06-29T18:02:34.000'
To delete a tile:
[36]:
byoc.delete_tile(created_collection, tile)
[36]:
''
Now the tile is gone forever.
[37]:
tiles = list(byoc.iter_tiles(created_collection))
[tile for tile in tiles if tile["id"] == created_tile["id"]]
[37]:
[]
Retrieve data from collection
Once we have a collection created and its tiles ingested, we can retrieve the data from said collection. We will be using ProcessAPI for this.
[38]:
data_collection = DataCollection.define_byoc(created_collection["id"])
Alternatively using dataclass:
data_collection = my_collection_dataclass.to_data_collection()
[39]:
tile_time = dateutil.parser.parse(tiles[0]["sensingTime"])
If using dataclass run:
tile_time = tile_dataclass.sensing_time
Below we’re going to request a false color image of Caspian Sea.
[40]:
caspian_sea_bbox = BBox((49.9604, 44.7176, 51.0481, 45.2324), crs=CRS.WGS84)
[41]:
false_color_evalscript = """
//VERSION=3
function setup() {
return {
input: ["B08","B04","B03", "dataMask"],
output: { bands: 4 },
};
}
var f = 2.5/10000;
function evaluatePixel(sample) {
return [f*sample.B08, f*sample.B04, f*sample.B03, sample.dataMask];
}
"""
request = SentinelHubRequest(
evalscript=false_color_evalscript,
input_data=[SentinelHubRequest.input_data(data_collection=data_collection, time_interval=tile_time)],
responses=[SentinelHubRequest.output_response("default", MimeType.PNG)],
bbox=caspian_sea_bbox,
size=bbox_to_dimensions(caspian_sea_bbox, 100),
config=config,
)
[42]:
data = request.get_data()[0]
[43]:
fig, ax = plt.subplots(figsize=(15, 10))
ax.imshow(data)
ax.set_title(tile_time.date().isoformat(), fontsize=10)
plt.tight_layout()

Configure collections
In the above case, bands are not defined before the tile ingestion process. BYOC service then automatically configures bands based on the first ingested tile and names the band after the filename unless a band placeholder is defined (see creating a new tile section). If the first ingested tile is a multi-band TIFF, the band index in 1-based numbering is also added at the end, e.g., the bands in a multi-band file named RGB.tiff
would
be named RGB_1
, RGB_2
, etc.
The automatic configuration generally configures all bands in a TIFF; however, BYOC service also allows user to configure bands manually. This can be useful when users have a multi-band TIFF but are only interested in ingesting part of the bands. Instead of trying to separate a multi-band TIFF into several single-band TIFFs, one can configure bands manually and ingest those specific bands only.
Automatic band configuration
Let’s create a new collection without bands configuration.
[7]:
byoc_auto_config = ByocCollection(name="byoc-auto-config", s3_bucket="byoc-config-demo")
byoc_auto_config_collection = byoc.create_collection(byoc_auto_config)
At this step, there is no band configured and we can check the configuration status using the following code.
[8]:
byoc_auto_config_collection = byoc.get_collection(byoc_auto_config_collection["id"])
byoc_auto_config_collection["isConfigured"]
[8]:
False
Now let’s try to ingest a true color TIFF which has red, green, and blue band.
[9]:
example_tile = ByocTile(path="(BAND)_COG.tiff", sensing_time=dt.datetime(2022, 12, 1))
added_tile = byoc.create_tile(byoc_auto_config_collection, example_tile)
added_tile
[9]:
{'id': '15659300-cf50-43da-afa7-ea4e72ed133c',
'created': '2023-01-16T14:00:09.079499Z',
'ingestionStart': '2023-01-16T14:00:09.079499Z',
'sensingTime': '2022-12-01T00:00:00Z',
'path': '(BAND)_COG.tiff',
'status': 'WAITING'}
[10]:
added_tile_info = byoc.get_tile(collection=byoc_auto_config_collection["id"], tile=added_tile["id"])
added_tile_info["status"]
[10]:
'WAITING'
After the tile being ingested successfully, we can see that the configuration status changes to True and the band information is stored in the additionalData
dictionary.
[14]:
byoc_auto_config_collection = byoc.get_collection(byoc_auto_config_collection["id"])
byoc_auto_config_collection["isConfigured"]
[14]:
True
[15]:
byoc_auto_config_collection["additionalData"]
[15]:
{'bands': {'RGB_1': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 1,
'sampleFormat': 'UINT'},
'RGB_2': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 2,
'sampleFormat': 'UINT'},
'RGB_3': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 3,
'sampleFormat': 'UINT'}}}
Manual band configuration
Now let’s try to create a pre-configured BYOC collection. In this example, the red, green, and blue band all need to be configured so that the true color TIFF can be fully ingested.
[16]:
band_config = ByocCollectionAdditionalData(
bands={
# source should be the string inside (BAND) placeholder
"R": ByocCollectionBand(source="RGB", band_index=1, bit_depth=8, sample_format="UINT"),
"G": ByocCollectionBand(source="RGB", band_index=2, bit_depth=8, sample_format="UINT"),
"B": ByocCollectionBand(source="RGB", band_index=3, bit_depth=8, sample_format="UINT"),
}
)
byoc_manual_config = ByocCollection(
name="byoc-manual-config", s3_bucket="byoc-config-demo", additional_data=band_config
)
byoc_manual_config_collection = byoc.create_collection(byoc_manual_config)
By checking the configuration status and additional data, we can see that the data collection is configured and the band information is stored in the additionalData
dictionary before any tiles being ingested.
[17]:
byoc_manual_config_collection["isConfigured"]
[17]:
True
[18]:
byoc_manual_config_collection["additionalData"]
[18]:
{'bands': {'R': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 1,
'sampleFormat': 'UINT'},
'G': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 2,
'sampleFormat': 'UINT'},
'B': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 3,
'sampleFormat': 'UINT'}}}
Then, we can ingest tiles as shown above.
[19]:
added_tile = byoc.create_tile(byoc_manual_config_collection, example_tile)
added_tile
[19]:
{'id': '6071cb25-472e-4dd6-8919-ebd1fdeedc11',
'created': '2023-01-16T14:07:03.313843Z',
'ingestionStart': '2023-01-16T14:07:03.313843Z',
'sensingTime': '2022-12-01T00:00:00Z',
'path': '(BAND)_COG.tiff',
'status': 'WAITING'}
[20]:
added_tile_info = byoc.get_tile(collection=byoc_manual_config_collection["id"], tile=added_tile["id"])
added_tile_info["status"]
[20]:
'INGESTED'
Ingest a specific band of the TIFF
One can also ingest a specific band from a multi-band TIFF. In this example, we will demonstrate how to only ingest green band from a true color TIFF.
Let’s start from creating a BYOC collection with green band being configured only.
[21]:
specific_band_config = ByocCollectionAdditionalData(
bands={
# here we specify the second band which is the green band to be ingested
"G": ByocCollectionBand(source="RGB", band_index=2, bit_depth=8, sample_format="UINT")
}
)
byoc_demo_manual_specific_band = ByocCollection(
name="byoc-specific-band-config", s3_bucket="byoc-config-demo", additional_data=specific_band_config
)
byoc_specific_band_config_collection = byoc.create_collection(byoc_demo_manual_specific_band)
This time we can see that the collection is configured and there is only one band in the additional data dictionary, meaning that BYOC service will only ingest the specified band that is pre-defined for the collection from the example multi-band TIFF.
[22]:
byoc_specific_band_config_collection["isConfigured"]
[22]:
True
[23]:
byoc_specific_band_config_collection["additionalData"]
[23]:
{'bands': {'G': {'bitDepth': 8,
'source': 'RGB',
'bandIndex': 2,
'sampleFormat': 'UINT'}}}
We can now use the same code to ingest the tile to the collection, but only the configured band will be ingested by the BYOC service.
[24]:
added_tile = byoc.create_tile(byoc_specific_band_config_collection, example_tile)
added_tile
[24]:
{'id': '7c518995-1c05-4f7c-883a-0630232ecb82',
'created': '2023-01-16T14:08:01.831044Z',
'ingestionStart': '2023-01-16T14:08:01.831044Z',
'sensingTime': '2022-12-01T00:00:00Z',
'path': '(BAND)_COG.tiff',
'status': 'WAITING'}
[25]:
added_tile_info = byoc.get_tile(collection=byoc_specific_band_config_collection["id"], tile=added_tile["id"])
added_tile_info["status"]
[25]:
'INGESTED'