[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータのビューアーを作ってみました
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 で作成したデータを使用してオブジェクト検出でモデルを作成してみました