ScriptProcessor で Sentinel-2 衛星データを使用して正規化植生差指数 (NDVI) を計算する - Amazon SageMaker AI

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

ScriptProcessor で Sentinel-2 衛星データを使用して正規化植生差指数 (NDVI) を計算する

次のコードサンプルは、Studio Classic ノートブック内の専用の地理空間イメージを使用して特定の地理的領域の正規化された差植物インデックスを計算し、SageMaker AI Python SDK から を使用して Amazon SageMaker SageMaker Processing で大規模なワークロードを実行する方法を示しています。 ScriptProcessor

このデモでは、地理空間カーネルとインスタンスタイプを使用する Amazon SageMaker Studio Classic ノートブックインスタンスも使用します。Studio Classic 地理空間ノートブックインスタンスを作成する方法については、「地理空間画像を使用して Amazon SageMaker Studio Classic ノートブックを作成する」を参照してください。

次のコードスニペットをコピーして貼り付けることで、独自のノートブックインスタンスでこのデモをフォローできます。

search_raster_data_collection を使用すると、サポートされているラスターデータコレクションのクエリを実行できます。この例では、Sentinel-2 衛星からプルされたデータを使用します。指定された対象エリア (AreaOfInterest) はアイオワ州北部の農村部で、時間範囲 (TimeRangeFilter) は 2022 年 1 月 1 日から 2022 年 12 月 30 日です。 AWS リージョン で使用可能なラスターデータコレクションを表示するには、 list_raster_data_collections を使用します。この API を使用したコード例については、ListRasterDataCollectionsAmazon SageMakerデベロッパーガイド」の「」を参照してください。

次のコード例では、Sentinel-2 ラスターデータコレクションである arn:aws:sagemaker-geospatial:us-west-2:378778860802:raster-data-collection/public/nmqj48dcu3g7ayw8 に関連付けられている ARN を使用します。

search_raster_data_collection API リクエストには次の 2 つのパラメータが必要です。

  • クエリを実行するラスターデータコレクションに対応する Arn パラメータを指定する必要があります。

  • また、Python ディクショナリを取り込む RasterDataCollectionQuery パラメータを指定する必要があります。

次のコード例には、 search_rdc_query 変数に保存されている RasterDataCollectionQuery パラメータに必要なキーと値のペアが含まれています。

search_rdc_query = { "AreaOfInterest": { "AreaOfInterestGeometry": { "PolygonGeometry": { "Coordinates": [[ [ -94.50938680498298, 43.22487436936203 ], [ -94.50938680498298, 42.843474642037194 ], [ -93.86520004156142, 42.843474642037194 ], [ -93.86520004156142, 43.22487436936203 ], [ -94.50938680498298, 43.22487436936203 ] ]] } } }, "TimeRangeFilter": {"StartTime": "2022-01-01T00:00:00Z", "EndTime": "2022-12-30T23:59:59Z"} }

search_raster_data_collection リクエストを実行するには、Sentinel-2 ラスターデータコレクション(arn:aws:sagemaker-geospatial:us-west-2:378778860802:raster-data-collection/public/nmqj48dcu3g7ayw8) の ARN を指定する必要があります。また、以前に定義された Python ディクショナリに渡す必要もあります。これはクエリパラメータを指定します。

## Creates a SageMaker Geospatial client instance sm_geo_client= session.create_client(service_name="sagemaker-geospatial") search_rdc_response1 = sm_geo_client.search_raster_data_collection( Arn='arn:aws:sagemaker-geospatial:us-west-2:378778860802:raster-data-collection/public/nmqj48dcu3g7ayw8', RasterDataCollectionQuery=search_rdc_query )

この API の結果をページ分割することはできません。search_raster_data_collection オペレーションによって返されるすべての衛星画像を収集するには、while ループを実装します。これにより、API レスポンス内の NextToken が確認されます。

## Holds the list of API responses from search_raster_data_collection items_list = [] while search_rdc_response1.get('NextToken') and search_rdc_response1['NextToken'] != None: items_list.extend(search_rdc_response1['Items']) search_rdc_response1 = sm_geo_client.search_raster_data_collection( Arn='arn:aws:sagemaker-geospatial:us-west-2:378778860802:raster-data-collection/public/nmqj48dcu3g7ayw8', RasterDataCollectionQuery=search_rdc_query, NextToken=search_rdc_response1['NextToken'] )

API レスポンスは、特定の画像バンドに対応する Assets キーの下にある URL のリストを返します。以下に、切り捨てられたバージョンの API レスポンスを示します。わかりやすくするために、一部の画像バンドは削除されています。

{ 'Assets': { 'aot': { 'Href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/15/T/UH/2022/12/S2A_15TUH_20221230_0_L2A/AOT.tif' }, 'blue': { 'Href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/15/T/UH/2022/12/S2A_15TUH_20221230_0_L2A/B02.tif' }, 'swir22-jp2': { 'Href': 's3://sentinel-s2-l2a/tiles/15/T/UH/2022/12/30/0/B12.jp2' }, 'visual-jp2': { 'Href': 's3://sentinel-s2-l2a/tiles/15/T/UH/2022/12/30/0/TCI.jp2' }, 'wvp-jp2': { 'Href': 's3://sentinel-s2-l2a/tiles/15/T/UH/2022/12/30/0/WVP.jp2' } }, 'DateTime': datetime.datetime(2022, 12, 30, 17, 21, 52, 469000, tzinfo = tzlocal()), 'Geometry': { 'Coordinates': [ [ [-95.46676936182894, 43.32623760511659], [-94.11293433656887, 43.347431265475954], [-94.09532154452742, 42.35884880571144], [-95.42776890002203, 42.3383710796791], [-95.46676936182894, 43.32623760511659] ] ], 'Type': 'Polygon' }, 'Id': 'S2A_15TUH_20221230_0_L2A', 'Properties': { 'EoCloudCover': 62.384969, 'Platform': 'sentinel-2a' } }

次のセクションでは、API レスポンスの 'Id' キーを使用してマニフェストファイルを作成します。

search_raster_data_collection API レスポンスの Id キーを使用して入力マニフェストファイルを作成する

処理ジョブを実行するときは、Amazon S3 からのデータ入力を指定する必要があります。入力データタイプはマニフェストファイルにすることができます。このファイルは個々のデータファイルを指し示します。処理する各ファイルにプレフィックスを追加することもできます。次のコード例では、マニフェストファイルを生成するフォルダを定義します。

SDK for Python (Boto3) を使用して、Studio Classic ノートブックインスタンスに関連付けられているデフォルトのバケットと実行ロールの ARN を取得します。

sm_session = sagemaker.session.Session() s3 = boto3.resource('s3') # Gets the default excution role associated with the notebook execution_role_arn = sagemaker.get_execution_role() # Gets the default bucket associated with the notebook s3_bucket = sm_session.default_bucket() # Can be replaced with any name s3_folder = "script-processor-input-manifest"

次に、マニフェストファイルを作成します。このファイルには、ステップ 4 の後半で処理ジョブを実行するときに処理する衛星画像の URL が保持されます。

# Format of a manifest file manifest_prefix = {} manifest_prefix['prefix'] = 's3://' + s3_bucket + '/' + s3_folder + '/' manifest = [manifest_prefix] print(manifest)

次のコードサンプルは、マニフェストファイルを作成する S3 URI を返します。

[{'prefix': 's3://sagemaker-us-west-2-111122223333/script-processor-input-manifest/'}]

処理ジョブを実行するにあたって、search_raster_data_collection レスポンスのすべてのレスポンス要素は必要ありません。

次のコードスニペットは、不要な要素である 'Properties''Geometry''DateTime' を削除します。'Id' のキーと値のペアである 'Id': 'S2A_15TUH_20221230_0_L2A' には、年と月が含まれます。次のコード例では、そのデータを解析し、Python ディクショナリ dict_month_items に新しいキーを作成します。値は、SearchRasterDataCollection クエリから返されるアセットです。

# For each response get the month and year, and then remove the metadata not related to the satelite images. dict_month_items = {} for item in items_list: # Example ID being split: 'S2A_15TUH_20221230_0_L2A' yyyymm = item['Id'].split("_")[2][:6] if yyyymm not in dict_month_items: dict_month_items[yyyymm] = [] # Removes uneeded metadata elements for this demo item.pop('Properties', None) item.pop('Geometry', None) item.pop('DateTime', None) # Appends the response from search_raster_data_collection to newly created key above dict_month_items[yyyymm].append(item)

このコード例では、.upload_file() API オペレーションを使用し、dict_month_itemsを JSON オブジェクトとして Amazon S3 にアップロードします。

## key_ is the yyyymm timestamp formatted above ## value_ is the reference to all the satellite images collected via our searchRDC query for key_, value_ in dict_month_items.items(): filename = f'manifest_{key_}.json' with open(filename, 'w') as fp: json.dump(value_, fp) s3.meta.client.upload_file(filename, s3_bucket, s3_folder + '/' + filename) manifest.append(filename) os.remove(filename)

このコード例は、Amazon S3 にアップロードされた他のすべてのマニフェストを指す親の manifest.json ファイルをアップロードします。また、ローカル変数 s3_manifest_uri へのパスも保存されます。ステップ 4 で処理ジョブを実行するときに、その変数を再度使用して入力データのソースを指定します。

with open('manifest.json', 'w') as fp: json.dump(manifest, fp) s3.meta.client.upload_file('manifest.json', s3_bucket, s3_folder + '/' + 'manifest.json') os.remove('manifest.json') s3_manifest_uri = f's3://{s3_bucket}/{s3_folder}/manifest.json'

入力マニフェストファイルを作成してアップロードしたら、処理ジョブでデータを処理するスクリプトを作成できるようになります。このスクリプトは衛星画像からのデータを処理し、NDVI を計算して、結果を別の Amazon S3 の場所に返します。

NDVI を計算するスクリプトを作成する

Amazon SageMaker Studio Classic は、%%writefile セルマジックコマンドの使用をサポートしています。このコマンドでセルを実行すると、その内容がローカルの Studio Classic ディレクトリに保存されます。これは NDVI の計算に固有のコードです。ただし、処理ジョブ用に独自のスクリプトを作成する場合は、以下が役立ちます。

  • 処理ジョブコンテナでは、コンテナ内のローカルパスは /opt/ml/processing/ で始まる必要があります。この例では、 input_data_path = '/opt/ml/processing/input_data/' processed_data_path = '/opt/ml/processing/output_data/' はそのように指定されています。

  • Amazon SageMaker Processing では、処理ジョブが実行するスクリプトを使用して、処理されたデータを Amazon S3 に直接アップロードできます。そのためには、ScriptProcessor インスタンスに関連付けられている実行ロールに、S3 バケットへのアクセスに必要な要件があることを確認してください。処理ジョブを実行するときに出力パラメータを指定することもできます。詳細については、Amazon SageMaker Python SDK.run() API オペレーションを参照してください。このコード例では、データ処理の結果が Amazon S3 に直接アップロードされます。

  • 処理ジョブにアタッチされている Amazon EBScontainer のサイズを管理するには、volume_size_in_gb パラメータを使用します。コンテナのデフォルトサイズは 30 GB です。オプションで Python ライブラリの Garbage Collector を使用して、Amazon EBS コンテナ内のストレージを管理することもできます。

    次のコード例では、処理ジョブコンテナに配列をロードします。配列が蓄積されてメモリが一杯になると、処理ジョブはクラッシュします。このクラッシュを防ぐために、次の例には、処理ジョブのコンテナから配列を削除するコマンドが含まれています。

%%writefile compute_ndvi.py import os import pickle import sys import subprocess import json import rioxarray if __name__ == "__main__": print("Starting processing") input_data_path = '/opt/ml/processing/input_data/' input_files = [] for current_path, sub_dirs, files in os.walk(input_data_path): for file in files: if file.endswith(".json"): input_files.append(os.path.join(current_path, file)) print("Received {} input_files: {}".format(len(input_files), input_files)) items = [] for input_file in input_files: full_file_path = os.path.join(input_data_path, input_file) print(full_file_path) with open(full_file_path, 'r') as f: items.append(json.load(f)) items = [item for sub_items in items for item in sub_items] for item in items: red_uri = item["Assets"]["red"]["Href"] nir_uri = item["Assets"]["nir"]["Href"] red = rioxarray.open_rasterio(red_uri, masked=True) nir = rioxarray.open_rasterio(nir_uri, masked=True) ndvi = (nir - red)/ (nir + red) file_name = 'ndvi_' + item["Id"] + '.tif' output_path = '/opt/ml/processing/output_data' output_file_path = f"{output_path}/{file_name}" ndvi.rio.to_raster(output_file_path) print("Written output:", output_file_path)

これで、NDVI を計算するスクリプトが完成しました。次に、ScriptProcessor のインスタンスを作成し、処理ジョブを実行します。

ScriptProcessor クラスのインスタンスを作成する

このデモでは、Amazon SageMaker Python SDK を介して利用可能な ScriptProcessor クラスを使用します。まず、クラスのインスタンスを作成してから、.run() メソッドを使用して処理ジョブを開始する必要があります。

from sagemaker.processing import ScriptProcessor, ProcessingInput, ProcessingOutput image_uri = '081189585635.dkr.ecr.us-west-2.amazonaws.com/sagemaker-geospatial-v1-0:latest' processor = ScriptProcessor( command=['python3'], image_uri=image_uri, role=execution_role_arn, instance_count=4, instance_type='ml.m5.4xlarge', sagemaker_session=sm_session ) print('Starting processing job.')

処理ジョブを開始するときは、ProcessingInput オブジェクトを指定する必要があります。そのオブジェクトで、以下を指定します。

  • ステップ 2 で作成したマニフェストファイルへのパス s3_manifest_uri。これは、コンテナへの入力データのソースです。

  • 入力データを保存するコンテナのパス。これは、スクリプトで指定したパスと一致する必要があります。

  • 入力を "ManifestFile" として指定するには、s3_data_type パラメータを使用します。

s3_output_prefix_url = f"s3://{s3_bucket}/{s3_folder}/output" processor.run( code='compute_ndvi.py', inputs=[ ProcessingInput( source=s3_manifest_uri, destination='/opt/ml/processing/input_data/', s3_data_type="ManifestFile", s3_data_distribution_type="ShardedByS3Key" ), ], outputs=[ ProcessingOutput( source='/opt/ml/processing/output_data/', destination=s3_output_prefix_url, s3_upload_mode="Continuous" ) ] )

次のコード例では、 .describe() メソッドを使用して処理ジョブの詳細を取得します。

preprocessing_job_descriptor = processor.jobs[-1].describe() s3_output_uri = preprocessing_job_descriptor["ProcessingOutputConfig"]["Outputs"][0]["S3Output"]["S3Uri"] print(s3_output_uri)

matplotlib を使用して結果を視覚化する

Matplotlib Python ライブラリを使用すると、ラスターデータをプロットできます。データをプロットする前に、Sentinel-2 衛星のサンプル画像を使用して NDVI を計算する必要があります。次のコード例では、.open_rasterio() API オペレーションを使用して画像配列を開き、nir および red の画像バンドを使用して Sentinel-2 衛星データから NDVI を計算します。

# Opens the python arrays import rioxarray red_uri = items[25]["Assets"]["red"]["Href"] nir_uri = items[25]["Assets"]["nir"]["Href"] red = rioxarray.open_rasterio(red_uri, masked=True) nir = rioxarray.open_rasterio(nir_uri, masked=True) # Calculates the NDVI ndvi = (nir - red)/ (nir + red) # Common plotting library in Python import matplotlib.pyplot as plt f, ax = plt.subplots(figsize=(18, 18)) ndvi.plot(cmap='viridis', ax=ax) ax.set_title("NDVI for {}".format(items[25]["Id"])) ax.set_axis_off() plt.show()

上記のコード例の出力は、NDVI 値がオーバーレイされた衛星画像です。1 に近い NDVI 値は、植物が多く存在することを示し、0 に近い値は植物が全く存在しないことを示します。

NDVI が上に重ねられたアイオワ北部の衛星画像

以上で、ScriptProcessor を使用するデモは完了です。