[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータのビューアーを作ってみました

2020.04.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

CX事業本部の平内(SIN)です。

Amazon SageMaker Ground Truth (以下、Ground Truth)では、作業を完了したデータをコンソール上で確認できます。

しかし、1ページに表示できる数が固定となっているため、データが大量にあると、全体を見渡すのが少し辛いかも知れません。

今回は、物体検出(Bounding box)用に作成されたデータセットを、ローカルで一覧するビューアーを作ってみました。

2 Ground Truthの出力

最初に、Ground Truthの出力である、画像とアノテーションデータ(output.manifest)をローカルにダウン ロードします。

output.manifestは、各行が各画像用のアノテーション情報であるJSONになっています。(以下、参考)

3 ビューア

ビューアは、動的に作成するindex.htmlで実現されています。下記の項目を設定して実行することで、index.htmlが生成されます。

  • targetPath Ground Truthの出力をダウンロードした、上記のフォルダ

index.py

import json

# 定義
targetPath = '/Users/hirauchi.shinichi/Downloads/VoTT-DataSet/GroundTruth'
manifest = 'output.manifest'

def main():
# ベースとなるのHTMLを取得
with open("./base.html") as f:
html = f.read()
# アノテーション(output.manifest)を読み込む
with open("{}/{}".format(targetPath, manifest)) as f:
lines = f.read()

# ベースHTMLの置き換え(//$$$[TargetPath]$$$)
html = html.replace("//$$$[TargetPath]$$$", "\"" + targetPath + "\"" )
text = ''
for line in lines.split('\n'):
if(line!=''):
text += "outputManifest.push(JSON.parse('"+ line +"'))\n"

# ベースHTMLの置き換え(//$$$[Manifest]$$$)
html = html.replace("//$$$[Manifest]$$$",text)

# index.htmlの保存
with open("./index.html", 'w') as f:
f.write(html)

main()

上記のindex.pyは、ベースとなるhtmlファイルを読み込み、output.manifestの内容(//$$$[Manifest]$$$))とターゲットとなるフォルダ(//$$$[TargetPath]$$$))を上書きしています。

ベースとなっているhtmlファイルは、以下のとおりです。

base.html

<br /><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<center></center> 
<h1>GroundTrurh Viewer</h1>
<input id="scale" max="1" min="0.1" step="0.1" type="range" value="0.5" />
<div id="contents"></div>
 

<script>
    const outputManifest = [];
    //$$$[Manifest]$$$

    const targetPath = //$$$[TargetPath]$$$

    class Data {
        constructor(json) {
            const tmp = json["source-ref"].split('/');
            this.imgName = tmp[tmp.length-1];

            let projectName = '';
            Object.keys(json).forEach(key => {
                const index = key.indexOf("-metadata");
                if(index != -1){
                    projectName = key.substr(0,index)
                }
            })
            this.size = json[projectName]["image_size"][0];
            this.annotations = json[projectName]["annotations"];
            this.class_map = json[projectName + "-metadata"]["class-map"];
        }
    }

    function createRgba(color, alpha) {
        return "rgba(" + [color[0], color[1], color[2], alpha] + ")"; 
    }

    function createColor(){
        return [Math.random() * 256, Math.random() * 256, Math.random() * 256]
    }

    function drawText(ctx, x, y, fontsize, color, text){
        ctx.font = `${fontsize}pt Arial`;
        const area = ctx.measureText(text);
        const th = area.actualBoundingBoxAscent * 1.5;
        const tw = area.width * 1.2;

        ctx.fillStyle = createRgba(color, 1);
        ctx.fillRect(x, y - th,  tw, th); 
        ctx.fillStyle = "white";
        ctx.fillText(text, x + fontsize/3, y - fontsize/4)
    }

    function draw(canvas, targetPath, scale, colors, data){
        const image = new Image();
        image.addEventListener("load",() => {
            const width = image.naturalWidth * scale;
            const height = image.naturalHeight * scale
            canvas.width = width;
            canvas.height = height;
            const ctx = canvas.getContext("2d");
            ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, 0, width, height);
            data.annotations.forEach( annotation =>{
                const i = annotation.class_id;
                const x = annotation.left * scale;
                const y = annotation.top * scale;
                const w = annotation.width * scale;
                const h = annotation.height * scale;
                const name = data.class_map[i];

                // 塗りつぶし
                ctx.fillStyle = createRgba(colors[i], 0.3);
                ctx.fillRect(x, y, w, h); 

                // 枠線
                ctx.strokeStyle = createRgba(colors[i], 1);
                ctx.lineWidth = 2;
                ctx.strokeRect(x, y, w, h); 

                // ラベル名
                drawText(ctx, x, y, width/35, colors[i], name)
            })
            // ファイル名
            const fontSize = width/35;
            drawText(ctx, 0, fontSize * 1.5, fontSize, [0,0,0], data.imgName);
        });
        image.src = `${targetPath}/${data.imgName}`;
    }

    // 再表示
    function refresh(dataList, scale, colors){
        // 表示エリアのノード取得
        const contents = $("#contents");
        // 全て削除
        contents.empty();

        // 各データの描画
        dataList.forEach( data => {
            const div = document.createElement('span');
            const canvas = document.createElement('canvas');
            // const name = document.createTextNode(data.imgName);
            // div.append(name)
            div.append(canvas)
            contents.append(div)
            draw(canvas, targetPath, scale, colors, data);
        })
    }

    function main(){

        const colors = [];
        Array.from(Array(100).keys()).forEach( ()=>{
            colors.push(createColor())    
        })
        // output.manifestからデータを読み込む
        const dataList = [];
        outputManifest.forEach(json => {
            dataList.push(new Data(json))
        })

        // 各データの描画
        refresh(dataList, 0.5, colors);

        $('#scale').on('input change', () => {
            refresh(dataList, $("#scale").val(), colors);
        });

    }    
    main();

</script>

4 動作確認

生成されたindex.htmlを開くと、アノテーションされた画像が確認できます。

タイトルの下のスライダーで、サイズを0.1倍〜1倍まで変更可能です。

ページ変更はなく、全てのデータが、一覧されます。

5 最後に

今回は、Ground Truthで作成したデータをローカルで一覧するビューアーを作成してみました。

実は、最近、Ground Truthを中心に、オブジェクト検出のためのデータセットの作成環境を整備しています。ビューアーは、この内の⑤になってます。

①VoTTで作成したデータをCustom Labelsで利用可能なAmazon SageMaker Ground Truth形式に変換してみました

②Amazon SageMaker Ground Truth で作成したデータをオブジェクト検出で利用可能なイメージ形式に変換してみました

③Amazon SageMaker Ground Truth で作成したデータを使用してオブジェクト検出でモデルを作成してみました

④Amazon SageMaker Ground Truth で作成したデータをVoTT形式のデータに変換してみました