- 投稿日:2019-12-11T23:57:38+09:00
「結び目」の図を簡単に描きたい
「結び目」について
この記事でいう「結び目」というのは、一つの閉じた円環がどのように絡まっているかを分類したものです。
参照:結び目理論(Wikipedia)「結び目」に関する文書などを書く際に、簡単に結び目の画像を描くことが出来ないかと考えて、調査してみたことと合わせて記事を書いてみたいと思います。なお、今回の記事は、計算によって曲線を生成する、ということに着目したもので、「結び目理論」とは本質的に関係がありません。
グラフィックツールで描く
手作業で描く手順
「交叉」している部分以外は、各グラフィックツールで閉曲線を描くことになるので省略します。
「交叉」している部分の描き方は様々な方法があると思いますが、今回は以下のようなやり方で表現しています。
- 下の線を「黒・細いストローク」で描く
- 交叉部分によって下の線を隠すため、上の線を「白・太いストローク」で描く
- 上の線を「黒・細いストローク」で描く
難点
- 曲線を上手に描くのが難しい
多くのグラフィックツールでは曲線を描く場合に、「ベジェ曲線」を使用して編集します。しかし、多数の点を滑らかに通過するような曲線を手作業で描くのは、ツールに慣れていないとなかなか骨が折れる作業です。
- 交叉部分を作るのが面倒
前述した「交叉する部分」の編集は単純ながら、グラフィックツールを使って手作業で行うのはなかなか面倒です。結び目が複雑になってくると、この交叉する箇所が多くなり編集時間や間違いの可能性が増えることが予想されます。
自動的に描くために
今回は、指定した点を通り、SVGを使って滑らかな曲線を表現する調整を計算により求め、また、交叉する部分のSVG編集を同時に行う、ということを目標とします。
入力は、グラフィックツール(Inkscapeを使用)により作成したSVGとして、下記のようなSVG画像を出力します。基本の3次Bezier曲線
SVGでサポートされている曲線で、簡単な計算式によって様々な形の弧からなる曲線を描くことができます。
Bézier curves, (Cubic Bézier curves)
B(t) = (1-t)^3 P_0 + 3(1-t)^2t P_1 + 3(1-t)t^2 P_2 + t^3 P_3 \hspace{10mm} (0 \leq t \leq 1)このベジェ曲線の複数の弧を使って、指定した点を通る曲線を生成します。
Catmull-Rom曲線
Bezier曲線を使って、指定した点を滑らかにつなぐには上記の$P_1$、$P_2$を別途計算することが必要になります。今回はCatmull-Rom曲線というものを利用して、Bezier曲線を生成しました。
"Centripetal Catmull–Rom spline" (Wikipedia)
この曲線はBezier同様に簡単な式で表現できる上に、3次Bezier曲線と相互に変換することが可能です。入力として与えられた点を通るCatmull-Rom曲線をBezier曲線に変換することで、SVGの基本機能のみで描画することが可能になります。
Catmull-RomからBezierへの変換
図中、$P_0, P_1, P_2, P_3$ を通るCatmull-Rom曲線の弧 $P_1P_2$ に相当するBezier曲線の点 $b_0, b_1, b_2, b_3$ を求める変換式は、下のようになります。
\begin{align} b_0 & = P_1 \\ b_1 & = P_1 + \small{\frac{P_2 - P_0}{6}} \\ b_2 & = P_2 - \small{\frac{P_3 - P_1}{6}} \\ b_3 & = P_2 \end{align}(注) 一般的なCatmull-Rom曲線には曲がり具合を調整するために計算に $\alpha$ という設定値を用いていますが、今回は、簡単のため $\alpha = 0$ として計算しています。
作業、および計算処理の流れ
直線で概形を描く(手作業)
最初に入力するSVGファイルを作成します。ここは手作業です。
このファイルを作る際に、下記の制限を設定しています。
- 制限1:2つの線分が交叉する場合は端点以外の場所で交叉する
- 制限2:全体として1個の閉じた曲線を構成する
作成した曲線(線分で構成された閉曲線)をSVGファイルとして保存し、プログラムの入力とします。
SVGファイルを読み込み、path要素内のd属性を見つける
以下、node.jsを使用して作成したものについて説明します。
上記のSVGドキュメント内に存在するpath要素のd属性を読み込みます。
ここでは、jsdomを利用してSVGをDOMとして扱っています。const fs = require("fs"); const jsdom = require('jsdom'); const { JSDOM } = jsdom; fs.readFile(process.argv[2], 'utf8', function(error, data) { // obtain svg path string const dom = new JSDOM(data); const path_d = dom.window.document .querySelector("svg path") .getAttribute("d");path要素のd属性を解析して頂点を求める
path要素のd属性には、SVGの描画命令文が格納されています。命令文を解析して座標を計算し、頂点として必要なものを抽出します。
今回は、MoveTo命令とLineTo命令のみを読み取れば良いので、ごく簡単な解析で済ませています。
// parse path string to get end points let pathSentence = path_d.match(/[mzlhvcsqta][0-9\-., ]*/gi) for (let phrase of pathSentence) { let cmd = phrase[0]; if (cmd === "m" || cmd === "l") { // move and lineTo let points = phrase.slice(1) .trim() .split(/[\s,]+/) let x = points.shift(); let y = points.shift(); //... (以下略、必要であれば後述のソースコードを参照してください)交叉する弧を見つける
全ての2つの線分の組み合わせについて、「交叉するか否か」を判断し、その情報から配列を生成します。後の工程で曲線の弧が「上を通る」か「下を通る」かを切り替える場所となります。
なお、「入力の線分が交叉するなら、生成するCatmull-Rom曲線の弧が交叉する」、また「線分が交叉しないなら、弧は交叉しない」という前提です(その前提が成立するように入力用SVGファイルを作成する必要があります)。
function isCrossing(segments0, segments1) { let [p0, p1] = segments0; let [p2, p3] = segments1; let [x0, y0] = p0; let [x1, y1] = p1; let [x2, y2] = p2; let [x3, y3] = p3; // bounding boxes test let b = Math.max(x0, x1) > Math.min(x2, x3) && Math.min(x0, x1) < Math.max(x2, x3) && Math.max(y0, y1) > Math.min(y2, y3) && Math.min(y0, y1) < Math.max(y2, y3); if (b) { // cross product test let cp0 = (x2 - x0) * (y3 - y0) - (y2 - y0) * (x3 - x0); let cp1 = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1); let cp2 = (x0 - x2) * (y1 - y2) - (y0 - y2) * (x1 - x2); let cp3 = (x0 - x3) * (y1 - y3) - (y0 - y3) * (x1 - x3); return (cp0 * cp1 < 0) && (cp2 * cp3 < 0); } return false; }Catmull-Rom曲線をSVGのBezierで描く
前述の変換式を使って、Catmull-Rom曲線を生成するBezire曲線の各点を求めます。
// // convert Catmull-Rom endpoints to cubic Bezier nodes // function catmullRomToBezier(endPoints) { let bezierNode = []; bezierNode.push(endPoints[0]); endPoints.forEach((p, i, a) => { let P0 = a[(i - 1 + a.length) % a.length] let P1 = p let P2 = a[(i + 1) % a.length] let P3 = a[(i + 2) % a.length] // Catmull-Rom to Bezier conversion (alpha=0) let Cb1x = P1[0] + (P2[0] - P0[0]) / 6 let Cb1y = P1[1] + (P2[1] - P0[1]) / 6 let Cb2x = P2[0] - (P3[0] - P1[0]) / 6 let Cb2y = P2[1] - (P3[1] - P1[1]) / 6 // push cubic bezier points bezierNode.push([Cb1x, Cb1y]) bezierNode.push([Cb2x, Cb2y]) bezierNode.push(P2) }); return bezierNode; }交叉部分を作る
生成されたCatumull-Rom曲線(SVG内ではBezier曲線で表現)の弧で交叉する部分を作ります。
g要素をレイヤーとして用いることによって、基本的には手作業で作成したのと同じ表現方法です。出力
jsdomのDOMを利用して全体をSVGドキュメントとして完成し、最終的にテキストとして出力して終了です。
// // create simple svg document with a Catmull-Rom curve // function draw_svg(endPoints, crossing, viewBox) { // create document root and body const document = new JSDOM().window.document; const body = document.body; // create svg element const svgns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(svgns, "svg"); // ... (中略) // construct svg document and output as text body.appendChild(svg); return document.body.innerHTML; }完成したもの
以上を、コマンドラインから使用できるプログラムとして作成したものをGitHub上にまとめました。
node draw_knot.js input.svg > output.svg生成したSVGドキュメントは標準出力に表示されます。
なお、今のところ、Inkscapeで作成したSVGでのみ動作を確認しています。今回の記事の内容のデモンストレーションが目的であって、あまり汎用的なツールとなることを目的とはしておりません。動作不良などは悪しからずご了承ください。作図例(入力と出力)
まとめ
今回の調査の元々のきっかけは、様々なデータを可視化する際に、matplotlibやd3.jsを使ってカスタマイズした図を作るよりも、SVGなどの基本的な機能で描画したほうが手っ取り早く、見やすいものができるのではないか、という考えからSVGの基本機能を調査してみよう、というものでした。
簡素ではあるけれども、「特殊な用途のための図」にぴったりのものがない場合に、ひとつの選択肢として考慮してみるのも良いと思います。
参考にした記事など
Centripetal Catmull–Rom spline
Bézier curves, (Cubic Bézier curves)
A Primer on Bézier Curves: 34. Bézier curves and Catmull-Rom curves
- 投稿日:2019-12-11T12:16:54+09:00
【Windows版】Node.js上でTensorFlow.jsを使う準備
はじめに
クライアントに極力負担をかけたくない!
サーバで学習も推論もするんだ!JavaScriptすき!!
そんなとき、Node.js上でTensorFlow.jsを動かしたいと考え、環境構築をしたとき、思ったほどめんどくさかったのでメモ!!前提条件
- Node.jsがインストールされていること
- OSがWindows7以上であること
構築
手順
1.node-gyp
のインストール
2.tfjs-node
のインストール簡単そう...
node-gypの導入
node-gyp
とは?node-gypNode.js用のネイティブアドオンモジュールをコンパイルするためのNode.jsで記述されたクロスプラットフォームコマンドラインツールです
node-gyp
横文字ばっかりでさっぱり。
要するに他の言語で書かれたモジュールとかをNode.jsで使えるようにビルドするぜってことみたい。Pyhton2.7 と C++ビルドツールが入っていないと動作してくれないので、まずそいつらからインストールしていく
PowerShellを管理者権限で実行し、以下のコマンドを打ち込む。
※windows7を使っている人は.NET Framework 4.5.1
が必要だぞnpm install --global --production windows-build-tools
こいつはすげえ。ビルドツールとPyhton2.7を一緒に入れてくれる代物!!最高だ!!
windows-build-toolsそして
node-gyp
をグローバルインストール!npm -g install node-gyp
オワリ!!!
tfjs-nodeの導入
tfjs-node
とは?このリポジトリは、Node.jsランタイムの下でバックエンドJavaScriptアプリケーションでネイティブTensorFlow実行を提供し、内部のTensorFlow Cバイナリによって加速されます。TensorFlow.jsと同じAPIを提供します。
tfjs-node
Node.jsでTensorFlow.jsを使いたいならtfjs-node
入れろ!てことだ。CPU版
npm install @tensorflow/tfjs-node
GPU版
npm install @tensorflow/tfjs-node-gpu
これでオワリ!!!
実行するときは
//CPU版 import * as tf from '@tensorflow/tfjs-node'; //GPU版 import * as tf from '@tensorflow/tfjs-node-gpu';を書いてから実行!
最後に
私はこの作業でエラーに悩まされました。
こうして書いてみると意外に単純な作業だったんだなって思います。悩まされた原因の一つとして、英語の文献を適当に読んでいたからだと思います。
反省してます。これからはちゃんと隅々まで読みます。英語圏の人として生まれたかったぞ
多分ほとんどのエンジニアは一度はそう思ったことがあるはずだ
- 投稿日:2019-12-11T09:24:36+09:00
Transcribeの英語ミーティング文字起こしをmarkdownにする
fushimiです。
この記事は Wanoグループ Advent Calendar 2019 Advent Calendar 2019 の11日目の記事になります。(英語)会議 is ...
![]()
最近プロジェクトではニューヨークのチームと英語での会議をする機会が多いです。基本リスニングも不得手なので、会議同席中も熱が入って速度が速い時はなかなか聞き取れないことがあります。
そこで会議の後など、勉強/議事録がてらAmazon Transcribeでの音声文字起こしを見てわからなかったところの文脈を追ってみたりしています。
今回はTranscribeの出力をmarkdown化するやつをwebアプリに起こしてみました。制作物
リポジトリ:
wano/aws-transcribe-renderアプリケーションのページ:
https://wano.github.io/aws-transcribe-render/Amazon Transcribe
いわゆる文字起こしサービスです。S3上の音声ファイルを解析してくれます。
最近日本語対応したり東京リージョンで使えるようになったりしました。
ただ文字起こしをする、ってだけではなく、話者解析(誰がそのフレーズを喋っているか)が取得できるのがなかなか面白いところかな、と思います。
Termは割と適当なんだけど、話者解析もあって文脈が追えるので復習にはいいかな...というステータスのサービスです。ちょっとした文ならコンソール上で結果がプレビューできるのですが、ある程度の長さになると出力結果であるオリジナルのjsonを使うしかありません。
... Can you see the seats? No option. Hello. It's not"}],"speaker_labels":{"speakers":8,"segments":[{"start_time":"1.44","speaker_label":"spk_4","end_time":"2.35","items":[{"start_time":"1.44","speaker_label":"spk_4","end_time":"1.81"},{"start_time":"1.94","speaker_label":"spk_4","end_time":"2.35"}]},{"start_time":"11.94","speaker_label":"spk_4","end_time":"12.45","items":[{"start_time":"11.94","speaker_label":"spk_4","end_time":"12.45"}]},{"start_time":"13.71","speaker_label":"spk_4","end_time":"14.16","items":[{"start_time":"13.71","speaker_label":"spk_4","end_time":"14.16"}]},{"start_time":"14.71","speaker_label":"spk_4","end_time":"15.38","items":[{"start_time":"14.71","speaker_label":"spk_4","end_time":"15.38"}]},{"start_time":"16.24","speaker_label":"spk_4","end_time":"16.91","items":[{"start_time":"16.24","speaker_label":"spk_4","end_time":"16.91"}]},{"start_time":"25.86","speaker_label":"spk_1","end_time":"26.97", ...こういう感じ。なかなか辛い
初めはPHP製のパーサーaws-transcribe-transcriptを改変してコマンド叩いていたのですが、jsonパース/整形/改変くらいwebのクライアントサイドでサクッとやれるべきだよな...という感想があったので、今回のアドベントカレンダーを機にjsで書いてみました。
aws-transcribe-render
markdown化する、と書きましたが、mustache記法でテンプレを書いているだけなのでなんでもありといえばありです。
テンプレートの塊は、ある話者が話し始めてから終わるまでとなっています。使い方
事前: まずは文字起こし
まず、会話/会議の音声データをs3に上げ、transcribeのコンソールからjson化しておきます。
jsonをアプリに入力/テンプレート編集
そのjsonをこちらのアプリに入力します。
- speaker
- text
- time
がテンプレート変数として渡ってくるので、markdownでもhtmlでもお好みのフォーマットで出力できます。
話者名の上書き機能
Transcribeのデフォルトの話者名を上書きすることもできます。
spk_0 という身も蓋もないラベリングになってたりするので、多少記憶を掘り起こし話者の名前を書きましょう。結果
ここまでの結果が以下のような感じです。
あとはコピーして議事録に貼り付けるなど。
まとめ
これで、出力結果をinputするだけでさっくりと編集する機能ができました。
英語で行われるミーティングでまた試す予定ですが、そういえば日本語会話でTranscribeを試したことがないので、そちらの精度も気になりますね。課題
- 外に出しても差し支えないような会話でアプリにサンプルを載っけたかったんだけど、著作権フリーな「会話音声データ」ってなかなか見つからないですね....
- GUIと関係ないコア部分のロジックは切り離されていますが、肝心のnpmモジュール化とかはまだしていません。 1年以上ぶりくらいにReactを触ったら「useStateすげー!」「useEffectすげー!」ってなってたりしたのでそっちで時間が溶けました。
- XSS対策真面目にやっていません。mustacheのテンプレをそのまま出しています。どこか永続化するサイトに使うわけでもないのでどうということはないのですが。