Blockly.Msg["UGJ_DRAW_GRIDEYEDATA_TITLE"] = "赤外線アレイセンサ画像表示 %1 温度データ %2 温度範囲上限 %3 %4 温度範囲下限 %5 %6"; Blockly.Msg["UGJ_DRAW_GRIDEYEDATA_TOOLTIP"] = "AMG8833の温度データを、画像としてキャンバスに描画します。「着色」をチェックすると、温度範囲で設定されている色をつけて表示します。"; Blockly.Msg["UGJ_GRIDEYE_INIT_TITLE"] = "赤外線アレイセンサ(アドレス: %1 )を初期化"; Blockly.Msg["UGJ_GRIDEYE_INIT_TOOLTIP"] = "赤外線アレイセンサ AMG8833 の使用準備をします。"; Blockly.Msg["UGJ_GRIDEYE_THERMISTOR_TITLE"] = "赤外線アレイセンサ本体温度"; Blockly.Msg["UGJ_GRIDEYE_THERMISTOR_TOOLTIP"] = "AMG8833に内蔵されたサーミスタ(温度センサ)の値を取得します。"; Blockly.Msg["UGJ_GRIDEYE_READ_TITLE"] = "赤外線アレイセンサの値"; Blockly.Msg["UGJ_GRIDEYE_READ_TOOLTIP"] = "AMG8833から読み取った温度データを、8x8の配列で取得します。"; Blockly.Msg["UGJ_GRIDEYE_STOP_TITLE"] = "赤外線アレイセンサから切断"; Blockly.Msg["UGJ_GRIDEYE_STOP_TOOLTIP"] = "センサーとの接続を停止します。"; Blockly.Msg["UGJ_GRIDEYE_CANVAS_CREATE_TITLE"] = "赤外線アレイセンサデータ表示キャンバスを作成"; Blockly.Msg["UGJ_GRIDEYE_CANVAS_CREATE_TOOLTIP"] = "ディスプレイエリアにAMG8833データ表示用キャンバスを作成します。"; Blockly.Msg["UGJ_TEACHABLE_MACHINE_TITLE"] = "TensorFlow.jsによる画像分類器の準備"; Blockly.Msg["UGJ_TEACHABLE_MACHINE_TOOLTIP"] = "TensorFlow.jsにMobileNet, KNN Classifierを読み込んで、画像認識(分類)を行う準備をします。"; Blockly.Msg["UGJ_GRIDEYE_PREDICT_CLASS_TITLE"] = "赤外線アレイセンサの画像で推論を行う"; Blockly.Msg["UGJ_GRIDEYE_PREDICT_CLASS_TOOLTIP"] = "キャンバスに表示されたAMG8833の画像を元に画像分類の推論を行います。推論の結果として定義済みのラベルを返します。"; Blockly.Msg["UGJ_GRIDEYE_ADD_EXAMPLE_TITLE"] = "赤外線アレイセンサの画像にラベル %1 をつけてデータセットへ追加"; Blockly.Msg["UGJ_GRIDEYE_ADD_EXAMPLE_TOOLTIP"] = "キャンバスに表示されているAMG8833の画像にラベル(クラス名)をつけてデータセットへ追加します。"; Blockly.Msg["UGJ_TENSORSET_STRINGIFY_TITLE"] = "学習したクラスデータセットを文字列に変換"; Blockly.Msg["UGJ_TENSORSET_STRINGIFY_TOOLTIP"] = "学習したクラスデータセットを文字列に変換して保存します。"; Blockly.Msg["UGJ_TENSORSET_PARSE_TITLE"] = "クラスデータ文字列 %1 を画像分類器にセット"; Blockly.Msg["UGJ_TENSORSET_PARSE_TOOLTIP"] = "JSONテキストをパースして画像分類器に戻します。"; /******************* */ /** Init Grid-Eye ** */ /******************* */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_init", "message0": "%{BKY_UGJ_GRIDEYE_INIT_TITLE}", "args0": [ { "type": "field_dropdown", "name": "addr", "options": [ [ "0x68", "0x68" ], [ "0x69", "0x69" ] ] } ], "inputsInline": true, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_GRIDEYE_INIT_TOOLTIP}", "helpUrl": "", "style": "sensor_blocks" }]); Blockly.JavaScript['ugj_grideye_init'] = function (block) { var dropdown_addr = block.getFieldValue('addr'); // Blockly.JavaScript.provideFunction_( // 'require_gpio', [`const _pi = require('@ocoge.club/` + apptool.gpio_lib + `');`] // ); let modpath = apptool.path.join(apptool.blocks_dir, 'sensors', 'amg8833', `AMG8833x_${apptool.gpio_lib}.js`); Blockly.JavaScript.provideFunction_( 'require_amg8833', [`const _amg8833 = require('${modpath}');`] ); var code = `await _amg8833.init(${apptool.i2c_bus}, ${dropdown_addr}, window.addEventListener);\n`; return code;// }; /********************** */ /** Grid-Eye 本体温度 ** */ /********************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_thermistor", "message0": "%{BKY_UGJ_GRIDEYE_THERMISTOR_TITLE}", "output": "Number", "tooltip": "%{BKY_UGJ_GRIDEYE_THERMISTOR_TOOLTIP}", "helpUrl": "", "style": "sensor_blocks" }]); Blockly.JavaScript['ugj_grideye_thermistor'] = function (block) { var code = `await _amg8833.read_thermistor()`; return [code, Blockly.JavaScript.ORDER_NONE]; }; /**************************** */ /** Read Temperature Array ** */ /**************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_read", "message0": "%{BKY_UGJ_GRIDEYE_READ_TITLE}", "inputsInline": true, "output": "Array", "tooltip": "%{BKY_UGJ_GRIDEYE_READ_TOOLTIP}", "helpUrl": "", "style": "sensor_blocks" }]); Blockly.JavaScript['ugj_grideye_read'] = function (block) { var code = 'await _amg8833.read_temp_array()'; return [code, Blockly.JavaScript.ORDER_ATOMIC]; }; /******************* */ /** Stop Grid-Eye ** */ /******************* */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_stop", "message0": "%{BKY_UGJ_GRIDEYE_STOP_TITLE}", "inputsInline": true, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_GRIDEYE_STOP_TOOLTIP}", "helpUrl": "", "style": "sensor_blocks" }]); Blockly.JavaScript['ugj_grideye_stop'] = function (block) { var code = 'await _amg8833.stop();\n'; return code; }; /***************************** */ /** GridEye 表示キャンバス作成 ** */ /***************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_canvas_create", "message0": "%{BKY_UGJ_GRIDEYE_CANVAS_CREATE_TITLE}", "inputsInline": true, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_GRIDEYE_CANVAS_CREATE_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_grideye_canvas_create'] = function (block) { var code = `let _grideye_canvas = document.createElement('canvas'); _grideye_canvas.setAttribute('width', 8); _grideye_canvas.setAttribute('height', 8); _grideye_canvas.className = 'subdisplay'; _grideye_canvas.style.width = '160px'; _grideye_canvas.style.height = '160px'; _grideye_canvas.id = 'subcanvas'; document.getElementById('display_area').appendChild(_grideye_canvas); _grideye_ctx = _grideye_canvas.getContext('2d', {willReadFrequently: true}); _grideye_imgData = _grideye_ctx.createImageData(8, 8); `; return code; }; /********************************************** */ /** Draw IR Array Data to Image Data ** */ /********************************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_draw_grideyedata", "message0": "%{BKY_UGJ_DRAW_GRIDEYEDATA_TITLE}", "args0": [ { "type": "input_dummy" }, { "type": "input_value", "name": "amg8833data", "check": "Array", "align": "RIGHT" }, { "type": "field_colour", "name": "color_high", "colour": "#ff0000" }, { "type": "input_value", "name": "temp_high", "check": "Number", "align": "RIGHT" }, { "type": "field_colour", "name": "color_low", "colour": "#3333ff" }, { "type": "input_value", "name": "temp_low", "check": "Number", "align": "RIGHT" } ], "inputsInline": false, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_DRAW_GRIDEYEDATA_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_draw_grideyedata'] = function (block) { var value_amg8833data = Blockly.JavaScript.valueToCode(block, 'amg8833data', Blockly.JavaScript.ORDER_ATOMIC); var colour_color_high = block.getFieldValue('color_high'); var value_temp_high = Blockly.JavaScript.valueToCode(block, 'temp_high', Blockly.JavaScript.ORDER_ATOMIC); var colour_color_low = block.getFieldValue('color_low'); var value_temp_low = Blockly.JavaScript.valueToCode(block, 'temp_low', Blockly.JavaScript.ORDER_ATOMIC); var functionName = Blockly.JavaScript.provideFunction_( '_mapVal', ['const ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = (val, inMin, inMax, outMin, outMax) => {', `return (val - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;`, '}' ] ); // 温度カラー let hr, hg, hb, lr, lg, lb; hr = '0x' + colour_color_high.slice(1, 3); hg = '0x' + colour_color_high.slice(3, 5); hb = '0x' + colour_color_high.slice(5, 7); lr = '0x' + colour_color_low.slice(1, 3); lg = '0x' + colour_color_low.slice(3, 5); lb = '0x' + colour_color_low.slice(5, 7); var code = ` const _color_range = [[${lr}, ${hr}], [${lg}, ${hg}], [${lb}, ${hb}]]; let _grideye_data = ${value_amg8833data};//読み取りブロックを入力に直接接続できるようにする for (let raw = 0; raw < _grideye_canvas.height; raw++) { for (let col = 0; col < _grideye_canvas.width; col++) { for (let rgb = 0; rgb < 3; rgb++) { let pixel = ${functionName}(_grideye_data[raw][col], ${value_temp_low}, ${value_temp_high}, _color_range[rgb][0], _color_range[rgb][1]); _grideye_imgData.data[((raw * _grideye_canvas.width * 4) + col * 4) + rgb] = pixel; } _grideye_imgData.data[((raw * _grideye_canvas.width * 4) + col * 4) + 3] = 0xff; } } _grideye_ctx.putImageData(_grideye_imgData, 0, 0); `; return code; }; /**************************** */ /** Teachable Machine を開始** */ /**************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_teachable_machine", "message0": "%{BKY_UGJ_TEACHABLE_MACHINE_TITLE}", "inputsInline": true, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_TEACHABLE_MACHINE_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_teachable_machine'] = function (block) { Blockly.JavaScript.provideFunction_( 'require_ts', [`const _tf = require('@tensorflow/tfjs-node');`] ); Blockly.JavaScript.provideFunction_( 'require_mobilenet', [`const _mobilenet = require('@tensorflow-models/mobilenet');`] ); Blockly.JavaScript.provideFunction_( 'require_knn', [`const _knnClassifier = require('@tensorflow-models/knn-classifier');`] ); var code = ` const _net = await _mobilenet.load({ version: 1, alpha: 0.25 }); // 高速・低精度 const _classifier = _knnClassifier.create(); `; return code; }; /************************* */ /** GridEye で推論を行う ** */ /************************* */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_predict_class", "message0": "%{BKY_UGJ_GRIDEYE_PREDICT_CLASS_TITLE}", "inputsInline": true, "output": "Number", "tooltip": "%{BKY_UGJ_GRIDEYE_PREDICT_CLASS_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_grideye_predict_class'] = function (block) { var functionName = Blockly.JavaScript.provideFunction_( // left output にするための関数化 '_predictClass', [ `if (_confidence === undefined) var _confidence;`, `const ${Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_} = async (img, clsfr, mblnet) => {`, `if (clsfr.getNumClasses() > 0) {`, `const result = await clsfr.predictClass(mblnet.infer(img, 'conv_preds'));`, `_confidence = result.confidences[result.label];`, `return result.label;`, `}`, `else return 0;`, `}` ] ); var code = `await ${functionName}(_grideye_canvas, _classifier, _net)`; return [code, Blockly.JavaScript.ORDER_NONE]; }; /******************************************** */ /** ラベルをつけて Example をデータセットに追加 ** */ /******************************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_grideye_add_example", "message0": "%{BKY_UGJ_GRIDEYE_ADD_EXAMPLE_TITLE}", "args0": [ { "type": "input_value", "name": "class_id", "check": "Number" } ], "inputsInline": true, "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_GRIDEYE_ADD_EXAMPLE_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_grideye_add_example'] = function (block) { var value_class_id = Blockly.JavaScript.valueToCode(block, 'class_id', Blockly.JavaScript.ORDER_ATOMIC); var code = `_classifier.addExample (_net.infer(_grideye_canvas, true), ${value_class_id});`; return code; }; /*************************** */ /** 学習したクラスを文字列化 ** */ /*************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_tensorset_stringify", "message0": "%{BKY_UGJ_TENSORSET_STRINGIFY_TITLE}", "output": null, "tooltip": "%{BKY_UGJ_TENSORSET_STRINGIFY_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_tensorset_stringify'] = function (block) { var code = `JSON.stringify( Object.entries(_classifier.getClassifierDataset()).map(([label, data])=>[label, Array.from(data.dataSync()), data.shape]) )`; return [code, Blockly.JavaScript.ORDER_NONE]; }; /***************************************** */ /** jsonをデータセットに戻して分類器にセット ** */ /***************************************** */ Blockly.defineBlocksWithJsonArray([{ "type": "ugj_tensorset_parse", "message0": "%{BKY_UGJ_TENSORSET_PARSE_TITLE}", "args0": [ { "type": "input_value", "name": "class_data_json", "check": "String" } ], "previousStatement": null, "nextStatement": null, "tooltip": "%{BKY_UGJ_TENSORSET_PARSE_TOOLTIP}", "helpUrl": "", "style": "multimedia_blocks" }]); Blockly.JavaScript['ugj_tensorset_parse'] = function (block) { var value_class_data_json = Blockly.JavaScript.valueToCode(block, 'class_data_json', Blockly.JavaScript.ORDER_ATOMIC); var code = `_classifier.setClassifierDataset( Object.fromEntries( JSON.parse(${value_class_data_json}).map(([label, data, shape])=>[label, _tf.tensor(data, shape)]) ) );`; return code; }; // 温度ブロック(ツールボックス上非表示) Blockly.defineBlocksWithJsonArray([{ "type": "ugj_temp", "message0": "%1", "args0": [ { "type": "field_slider", "name": "temp", "value": 15, "min": 0, "max": 40, "precision": 1 } ], "inputsInline": true, "output": "Number", "tooltip": "", "helpUrl": "", "style": "math_blocks" }]); Blockly.JavaScript['ugj_temp'] = function (block) { var number_temp = block.getFieldValue('temp'); var code = `${number_temp}`; return [code, Blockly.JavaScript.ORDER_NONE]; }; flyout_contents = flyout_contents.concat([ { "kind": "label", "text": "赤外線アレイセンサー(サーマルカメラ)AMG8833", "web-line": "4.0", "web-line-width": "200" }, { "kind": "block", "type": "ugj_grideye_init", "fields": { "addr": "0x69" } }, { "kind": "block", "type": "ugj_grideye_thermistor" }, { "kind": "block", "type": "ugj_grideye_read" }, { "kind": "block", "type": "ugj_grideye_stop" }, { "kind": "block", "type": "ugj_grideye_canvas_create" }, { "kind": "block", "type": "ugj_draw_grideyedata", "fields": { "color_high": "#ff0000", "color_low": "#3333ff" }, "inputs": { "temp_high": { "shadow": { "type": "ugj_temp", "fields": { "temp": "28" } } }, "temp_low": { "shadow": { "type": "ugj_temp", "fields": { "temp": "15" } } } } }, { "kind": "block", "type": "ugj_teachable_machine" }, { "kind": "block", "type": "ugj_grideye_predict_class" }, { "kind": "block", "type": "ugj_grideye_add_example", "inputs": { "class_id": { "shadow": { "type": "math_number", "fields": { "NUM": "0" } } } } }, { "kind": "block", "type": "ugj_tensorset_stringify" }, { "kind": "block", "type": "ugj_tensorset_parse" } ]);