

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

# `ScriptProcessor` で Sentinel-2 衛星データを使用して正規化植生差指数 (NDVI) を計算する
<a name="geospatial-custom-operations-procedure"></a>

次のコード例は、Studio Classic ノートブック内の専用地理空間画像を使用して特定の地理的領域の正規化植生差指数を計算し、SageMaker AI Python SDK から [https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ScriptProcessor](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ScriptProcessor) を使用して Amazon SageMaker Processing で大規模なワークロードを実行する方法を説明しています。

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

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

1. [`search_raster_data_collection` で、特定のラスターデータコレクションである Sentinel-2 を使用して、特定の時間範囲における特定の対象エリア (AOI) のクエリを実行します。](#geospatial-custom-operations-procedure-search)

1. [処理ジョブ中に処理されるデータを指定するマニフェストファイルを作成します。](#geospatial-custom-operations-procedure-manifest)

1. [NDVI を計算するデータ処理 Python スクリプトを作成します。](#geospatial-custom-operations-script-mode)

1. [`ScriptProcessor` インスタンスを作成し、Amazon SageMaker Processing ジョブを開始します](#geospatial-custom-operations-create)。

1. [処理ジョブの結果を視覚化します](#geospatial-custom-operations-visual)。

## `SearchRasterDataCollection` を使用して Sentinel-2 ラスターデータコレクションをクエリする
<a name="geospatial-custom-operations-procedure-search"></a>

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

次のコード例では、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'
    }
}
```

[次のセクション](#geospatial-custom-operations-procedure-manifest)では、API レスポンスの `'Id'` キーを使用してマニフェストファイルを作成します。

## `search_raster_data_collection` API レスポンスの `Id` キーを使用して入力マニフェストファイルを作成する
<a name="geospatial-custom-operations-procedure-manifest"></a>

処理ジョブを実行するときは、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\$1raster\$1data\$1collection レスポンスのすべてのレスポンス要素は必要ありません。

次のコードスニペットは、不要な要素である `'Properties'`、`'Geometry'`、`'DateTime'` を削除します。`'Id'` のキーと値のペアである `'Id': 'S2A_15TUH_20221230_0_L2A'` には、年と月が含まれます。次のコード例では、そのデータを解析し、Python ディクショナリ **dict\$1month\$1items** に新しいキーを作成します。値は、`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)
```

このコード例では、[https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/upload_file.html](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/upload_file.html) 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\$1manifest\$1uri** へのパスも保存されます。ステップ 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 を計算するスクリプトを作成する
<a name="geospatial-custom-operations-script-mode"></a>

Amazon SageMaker Studio Classic は、`%%writefile` セルマジックコマンドの使用をサポートしています。このコマンドでセルを実行すると、その内容がローカルの Studio Classic ディレクトリに保存されます。これは NDVI の計算に固有のコードです。ただし、処理ジョブ用に独自のスクリプトを作成する場合は、以下が役立ちます。
+ 処理ジョブコンテナでは、コンテナ内のローカルパスは `/opt/ml/processing/` で始まる必要があります。この例では、 **input\$1data\$1path = '/opt/ml/processing/input\$1data/' ** と **processed\$1data\$1path = '/opt/ml/processing/output\$1data/'** はそのように指定されています。
+ Amazon SageMaker Processing では、処理ジョブが実行するスクリプトを使用して、処理されたデータを Amazon S3 に直接アップロードできます。そのためには、`ScriptProcessor` インスタンスに関連付けられている実行ロールに、S3 バケットへのアクセスに必要な要件があることを確認してください。処理ジョブを実行するときに出力パラメータを指定することもできます。詳細については、*Amazon SageMaker Python SDK* の [`.run()` API オペレーション](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ScriptProcessor.run)を参照してください。このコード例では、データ処理の結果が Amazon S3 に直接アップロードされます。
+ 処理ジョブにアタッチされている Amazon EBScontainer のサイズを管理するには、`volume_size_in_gb` パラメータを使用します。コンテナのデフォルトサイズは 30 GB です。オプションで Python ライブラリの [Garbage Collector](https://docs.python.org/3/library/gc.html) を使用して、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` クラスのインスタンスを作成する
<a name="geospatial-custom-operations-create"></a>

このデモでは、Amazon SageMaker Python SDK を介して利用可能な [ScriptProcessor](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.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.')
```

処理ジョブを開始するときは、[https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ProcessingInput](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ProcessingInput) オブジェクトを指定する必要があります。そのオブジェクトで、以下を指定します。
+ ステップ 2 で作成したマニフェストファイルへのパス **s3\$1manifest\$1uri**。これは、コンテナへの入力データのソースです。
+ 入力データを保存するコンテナのパス。これは、スクリプトで指定したパスと一致する必要があります。
+ 入力を `"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()` メソッド](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ProcessingJob.describe)を使用して処理ジョブの詳細を取得します。

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

## `matplotlib` を使用して結果を視覚化する
<a name="geospatial-custom-operations-visual"></a>

[Matplotlib](https://matplotlib.org/stable/index.html) 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 が上に重ねられたアイオワ北部の衛星画像\]](http://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/images/ndvi-iowa.png)


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