銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

D3.js v4 でドラッグするには d3.drag() で behavior を取得する

f:id:takamints:20170418212231p:plain

D3.jsでドラッグイベントを処理する必要があったのですよ。

ほぼ初めてのD3ですからグーグル先生にいろいろ聞いて、「ほうほうなるほど」と学習していたのですけど、 ドラッグに関して各所で示されていたサンプル通りにやってみたら、まさかのエラー。

結局は、大きな問題ではありませんで、ひと言で言ってしまえばバージョン違いだったのですが、どうにも日本語の情報が少ないようなので書いておきます。

英語版です。v4に関して日本語の書籍が見当たらないです。

D3.js/v3でのコード

「D3.js ドラッグ」などと検索して出てくるサンプルコードの多くが、D3.jsのv3(バージョン3)のために書かれたコードで、最新のv4では動かないんですね。

v3でドラッグするには、d3.behavior.drag()で、Drag Behavior を取得するのですが、v4ではこれが動かない。

// D3.js v3でDrag Behaviorを取得

var drag = d3.behavior.drag(); //V4ではエラー

D3.js/v4でのOKコード

じゃあどうやるんだって―と、v4以降では以下のように、d3.drag()とするのだそうだ。

// D3.js/V4 でDrag Behaviorを取得

var drag = d3.drag(); // これでOK

リンク

リポジトリCHANGES にはしっかり書いてありました。

CHANGES
github.com

Dragに関する詳細は以下に
github.com

いやしかし

メジャーバージョンが上がっているので、APIに互換性がなくなってても文句は言わない約束だけど、 レガシーコードには警告を出すとかって対応があればモアベターね。

JavaScriptのラムダ式(アロー関数)は丸括弧で括らなければ即時実行できませんのね

f:id:takamints:20170328172435p:plain
Link: Flickr PAGE - CC BY 2.0

Node.jsで以下のようにラムダ式を即時実行していたのですが、ブラウザでは構文エラーとなって動かないんです。

(()=>{
    console.log("これ動きません");
}());

まさかコレが動かないとか思いもよらず。 どう見直しても問題があるとは思えなかったのだが動かないから仕方がない。

追記(2017-03-29)JavaScriptの言葉的には「ラムダ式」ではなく「アロー関数」のようですので、タイトルにのみ追記しました。

色々やってみた結果、ブラウザで動作させるには、以下のようにラムダ式全体を丸括弧で括る必要があるようですね。 しかし、なんだかカッコが多すぎ。ラムダ的に台無し感がありますなー。

((()=>{
    console.log("これでヨシ。だがしかし・・・");
})());

丸括弧なしでは関数オブジェクトとして評価されていないようです。 アロー演算子=>)の優先度の問題かな?

そもそも、どちらが正しいのかわからなかったので、調べてみると、どうやらECMAScript6の仕様のバグだそうで、 Node.jsでは便利な様に解釈して実装されているのかも知れませんが、今の所ブラウザではどうにもならないようですね。

d.hatena.ne.jp

即時実行するときはラムダ式を使わないというのが安全・安心? 関数内のthisが定義時にバインドされちゃうようなので、単なる無名関数の構文糖として使うのはマズいらしいし・・・

JavaScriptでマイクロ秒単位の定期処理を実行する(npm fractional-timer)

JavaScriptで1ミリ秒より短いインターバルタイマー処理を提供するモジュール fractional-timer のご紹介。

f:id:takamints:20170327172258p:plain
Link: Flickr PAGE - CC BY-SA 2.0

実際のところ、精度はよくありませんので、クリティカルな用途には向きません。 単純な処理をなるべく高速にタイマーで実行したいけど、標準タイマーの1ミリ秒では遅すぎる・・・といった時に使える感じ。 処理が重い場合は、呼び出し間隔は長くなります。

Chrome と Node.Js で、正しく動いていることを確認していますが、 FirefoxやEdgeでは、まともに動いていなかったので、ご注意下さい。

使い方

使い方は遅延時間に実数を指定できる点を除けば、標準のsetIntervalclearIntervalと同じです。 遅延時間は標準と同じくミリ秒単位で指定します(0.1=100マイクロ秒)。

以下のように呼び出すことで、タイマーの数と遅延時間等を調整して、定期処理を実行します。

(function() {
    "use strict";
    var ft = require("fractional-timer");
    var ftid = ft.setInterval(
        function(){
            ft.clearInterval(ftid);
        },
        0.001);// 1 microsecond
}());

インストー

npmでインストールすればNode.jsから使えます。

> npm install fraction-timer

ブラウザのWEBアプリでは、browserifyrequire するか、 scriptタグで読み込んで、FractionalTimerクラスの静的メソッドsetInterval/clearIntervalを直接使ってくださいね。

リポジトリ

www.npmjs.com

github.com

その他、能書き

やっていることは単純です。複数のインターバルタイマーで一つの処理を呼び出しているだけです。

例えば、1マイクロ秒で実行したい場合、1ミリ秒のタイマーを1000個使って呼び出すということです。

実際には、指定された間隔から算出される単位時間あたりの実行回数を計算し、タイマーの数と間隔を自動的に算出、ベストエフォートで呼び出します。

MZ-700フルJavaScriptエミュレータ」で、エミュレーションをワーカースレッドの定期処理で実装していて、当初は単一のインターバルタイマーで、最も高速になるように調整していたのですが、ある時タイマーが二重に動いてしまったことがあって、速度がなんと2倍程度に跳ね上がっていたのです。

「うわあ!世紀の大発見!!!」と思ったけれど、単一スレッド内ではシーケンシャル動作が保証されているので、当然の挙動なんですよね。

ただ、こういうことをやってる人があまりいないところを見ると、需要はないみたいなんですけどね(笑)

.NETのアプリケーション設定を編集可能なXMLに保存する

ここではアプリケーションの設定を編集可能なXMLファイルとして利用するための基本クラスとサンプルをご紹介。

.NETのプロジェクトには「アプリケーション構成ファイル」があって、アプリケーションの設定情報を書いておけるような印象を持ちますが、 実際には「構成情報」であり、インストーラーでセットアップすると、読み取り専用のアプリケーションフォルダに入ってしまい、編集するには管理者権限が必要になります。 (また、書き換えたつもりなのに元に戻るということも・・・どういう機構なのか知らないのですが・・・)

とにかくアプリケーション構成ファイル」は運用中の書き換えが、考慮されていないってことですね。

f:id:takamints:20170221171853p:plain

■■■ 目次 ■■■

  1. はじめに - 機能仕様
  2. XML設定クラス定義例:SampleSettingsクラス
  3. 設定ファイルの読み込み処理
  4. XMLファイルの例:SampleSettings.xml
  5. XML設定ファイルの基本クラス:XmlSettingFileクラス
  6. 設定ファイルはWindowsの特殊フォルダ以下へ
  7. 参考サイト
基礎からわかる C#
基礎からわかる C#
posted with amazlet at 17.02.21
シーアンドアール研究所 (2017-02-16)
売り上げランキング: 16,889

1. はじめに - 機能仕様

ここでやりたいことをザックリ以下に箇条書き。

  1. アプリ起動時に特定のフォルダに保存されたXMLの設定ファイルを読み込む。
  2. ファイルがない場合は、アプリ起動時に既定の内容で設定ファイルを作成する。
  3. 設定情報はクラスのインスタンスとしてプログラムからアクセスできる。
  4. 最低限プリミティブ型が正しく読み書きできること。

設定読み込み時の値のチェックはしていません。 バリデーション機能を持たせたメタクラスを導入すれば可能ですが、大げさになり過ぎるので、またおいおい。

2. XML設定クラス定義例:SampleSettingsクラス

以下がXML設定情報を保持するクラス。

設定ファイルに保存したい値をメンバとして定義したシンプルなクラスです。 XMLシリアライズを利用しているのでパブリックなデフォルトコンストラクタが必要です。 基本クラスの XmlSettingFileクラス は、下で説明します。

/// <summary>
/// XMLアプリケーション設定サンプル
/// </summary>
public class SampleSettings : Utils.XmlSettingFile
{
    /// <summary>
    /// 既定の設定情報を生成。
    /// (デフォルトコンストラクタは必ず必要)
    /// </summary>
    public SampleSettings() { }

    //以下のメンバーはXMLに保存される

    public string Name { get; set; } = "Koji Takami";
    public string Email { get; set; } = "vzg03566@gmail.com";
    public int FeatureOption { get; set; } = 0;
    public bool DebugOption { get; set; } = false;

    //保存したくないメンバーは以下のように宣言
    [System.Xml.Serialization.XmlIgnore]
    public string NotSaved;
}

3. 設定ファイルの読み込み処理

上のクラスのインスタンスXMLファイルから読み込むには、XmlSettingFile.Loadを使用します。 このメソッドには、保存場所のルートを決めるスペシャルフォルダの列挙子と、既定値を保持する設定情報のインスタンスを指定します。

以下のコードでは、設定ファイルを読み込んで、SampleSettingクラスのインスタンスを返します。

public partial class App : Application
{
    static public SampleSettings sampleSettings = new SampleSettings();
    public App()
    {
        try {
            sampleSettings = Utils.XmlSettingFile.Load(
                Environment.SpecialFolder.CommonApplicationData,
                new SampleSettings()) as SampleSettings;
        } catch (Exception ex) {
            Console.WriteLine("Error:{0}", ex.Message);
        } finally {
            sampleSettings.Save();
        }
    }
}
  • 上のコードで読み書きされる設定ファイルは、「C:\ProgramData\(CompanyName)\(ProductName)\(ProductVersion)\SampleSetting.xml」です。ここで、(CompanyName)(ProductName)(ProductVersion)アセンブリのファイルバージョン情報の設定値です。プロジェクトの初期状態ではCompanyNameが空白ですが、その部分抜きで正しいパスに生成されます。
  • 読み込み時にファイルがなければ、既定値のインスタンスを使用して作成されます。
  • ここでは、アプリケーションクラスのコンストラクタで読み出していますが、別のタイミングでも問題はありません。
  • 読み込んだ直後に保存しているのは、開発中に追加項目を反映したりObsoleteな設定項目をXMLから削除するためです。これがなくてもファイルがなければ作成されます。このSaveは、必ずしも必要ではありません。

4. XMLファイルの例:SampleSettings.xml

最初の実行時に生成されたファイルの中身は以下のようになります。 書き換えて読み込んだら、ちゃんと設定値が反映されます。それが目的ですからね。

<?xml version="1.0" encoding="utf-8"?>
<SampleSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Takami Koji</Name>
  <Email>vzg03566@gmail.com</Email>
  <FeatureOption>0</FeatureOption>
  <DebugOption>false</DebugOption>
</SampleSettings>

5. XML設定ファイルの基本クラス:XmlSettingFileクラス

以下が、このエントリーの御本尊。 派生クラスのメンバーをXMLファイルへシリアライズ/デシリアライズするクラス。 (名前空間は適当に変えてください)

ファイル名の決定や、アクセス権の設定などで長くなっていますが、やってることは意外にシンプル。XmlSerializerのお陰様。

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Xml.Serialization;

namespace StudyDotNet.Utils
{
    public class XmlSettingFile
    {
        /// <summary>
        /// 設定ファイルを読み込む
        /// </summary>
        /// <param name="folderId">
        ///     スペシャルフォルダを指定する</param>
        /// <param name="initial">
        ///     ファイルがない場合の既定値を持つ
        ///     インスタンス</param>
        /// <param name="filename">
        ///     ファイル名。
        ///     省略時はクラス名を採用する</param>
        /// <returns></returns>
        public static XmlSettingFile Load(
            Environment.SpecialFolder folderId,
            XmlSettingFile initial,
            string filename = null)
        {
            //ファイル名を生成
            string pathname = CreatePathname(
                folderId, initial, filename);

            //設定ファイルがなければ既定の値で生成する
            if (!File.Exists(pathname)) {
                Save(pathname, initial);
                AllowUsersReadAndWrite(pathname);
            }

            //設定ファイルを読み込む
            return Read(pathname, initial.GetType());
        }

        /// <summary>
        /// 設定ファイル名。
        /// 実際に読み込んだもの。
        /// 書き戻す場合にも利用する。
        /// </summary>
        [XmlIgnore] public string Filename { get; private set; }

        /// <summary>
        /// 設定ファイルへ書き戻す。
        /// プログラムから設定値を変更した場合などに使用。
        /// </summary>
        public void Save() { Save(Filename, this); }

        #region StaticPrivate

        /// <summary>
        /// 与えられた情報からファイル名を生成する
        /// </summary>
        /// <param name="folderId"></param>
        /// <param name="initial"></param>
        /// <param name="filename"></param>
        /// <returns></returns>
        static private string CreatePathname(
            Environment.SpecialFolder folderId,
            XmlSettingFile initial,
            string filename)
        {
            //ファイル名が指定されていないならクラス名
            if(filename == null) {
                filename = initial.GetType().Name;
            }

            //拡張子がXMLでない場合はXMLに変更する
            if(Path.GetExtension(filename) == "") {
                filename = Path.ChangeExtension(filename, "xml");
            }

            //ファイル名を決定
            string pathname = Path.Combine(
                CreateFolder(folderId), filename);

            return pathname;
        }

        /// <summary>
        /// スペシャルフォルダ以下のパスを生成
        /// </summary>
        /// <param name="folderId"></param>
        /// <returns></returns>
        static private string CreateFolder(
            Environment.SpecialFolder folderId)
        {
            //アセンブリのファイルバージョン情報を利用してフォルダを決定
            var verinfo = FileVersionInfo.GetVersionInfo(
                Assembly.GetExecutingAssembly().Location);

            // 空白の項目はパスに含まれない。
            // 例えば CompanyName が空の場合、
            // @"C:\ProgramData\ProductName\1.0.0.0" などとなる
            string path = Path.Combine(new string[] {
                Environment.GetFolderPath(folderId),
                verinfo.CompanyName,
                verinfo.ProductName,
                verinfo.ProductVersion,
            });

            //フォルダがないなら生成する
            if(!Directory.Exists(path)) {
                Directory.CreateDirectory(path);
            }

            return path;
        }

        /// <summary>
        /// 設定を設定ファイルに保存
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="setting"></param>
        static private void Save(
            string filename, XmlSettingFile setting)
        {
            var writer = new StreamWriter(
                filename, false, new UTF8Encoding(false));
            var xml = new XmlSerializer(setting.GetType());
            xml.Serialize(writer, setting);
            writer.Close();
        }

        /// <summary>
        /// 設定ファイルを読み込む
        /// </summary>
        /// <param name="filename">設定ファイル</param>
        /// <param name="type">設定データの型</param>
        /// <returns>type型のインスタンス。
        /// ファイルない場合はnullを返す。</returns>
        static private XmlSettingFile Read(
            string filename, Type type)
        {
            var reader = new StreamReader(
                filename, new UTF8Encoding(false));
            var xml = new XmlSerializer(type);
            var setting = xml.Deserialize(reader) as XmlSettingFile;
            reader.Close();
            if(setting != null)
            {
                setting.Filename = filename;
            }
            return setting;
        }

        /// <summary>
        /// 指定ファイルにUsersグループのユーザーの読み書き権限を与える
        /// </summary>
        /// <param name="filename"></param>
        static private void AllowUsersReadAndWrite(string filename) {
            FileSystemAccessRule rule = new FileSystemAccessRule(
                new NTAccount("Users"),
                FileSystemRights.Write | FileSystemRights.Read,
                AccessControlType.Allow);
            FileSecurity security = File.GetAccessControl(filename);
            security.AddAccessRule(rule);
            File.SetAccessControl(filename, security);
        }

        #endregion

    }
}

6. 設定ファイルはWindowsの特殊フォルダ以下へ

参考のため、アプリケーションの設定ファイルを保存するために適当なスペシャルフォルダを抜き出してみました。 下表はDOBON.NET プログラミング道:特殊ディレクトリのパスを取得するからの抜粋です。

Windows / .NET的に設定ファイルは、以下のようなフォルダ以下に、アプリケーションの企業名、製品名、バージョンなどのフォルダを掘って、保存するのが望ましいようです。

Environment.SpecialFolder列挙体のメンバ 具体例(参考)
1 CommonApplicationData C:\ProgramData
2 ApplicationData C:\Users\(UserName)\AppData\Roaming
3 LocalApplicationData C:\Users\(UserName)\AppData\Local
4 UserProfile C:\Users\(UserName)
6 MyDocuments C:\Users\(UserName)\Documents
5 CommonDocuments C:\Users\Public\Documents

全ユーザーに共通する設定なら1が良いと思います。ユーザー別なら2か3、4もあり。ただしローミングが必要なら2であるべきですね。

5と6については非推奨。Documentsに設定ファイルを置くのはちょっと違うかもしれないですからね。

7. 参考サイト

OpenCVの画像処理をお手軽に ― OpenCVフィルター処理ライブラリ cvImagePipeline のご紹介

画像処理・機械学習プログラミング OpenCV 3対応
浦西 友樹 青砥隆仁 井村誠孝 大倉史生 金谷一朗 小枝正直 中島悠太 藤本雄一郎 山口明彦 山本豪志朗
マイナビ出版 (2017-06-21)
売り上げランキング: 2,966

OpenCVの画像処理をお手軽にできるC++向けフィルター処理ライブラリ cvImagePipeline のご紹介。

画像処理の流れや各モジュールのパラメータの指定をXMLで記述できますので、画像処理の処理順の変更やパラメーター調整作業がはかどります。 複数のフィルターを組み合わせて独自のフィルターも作れます。

背景除去のサンプルプログラム

以下の画像は、ライブラリに含まれているサンプルプログラムの実行中画面です。

f:id:takamints:20161228110352p:plain

単純な背景除去の実装実験を行っている画面です。 入力画像や前処理中の画像などを、6分割した画面に統合して表示しています。 (カメラ入力や画面の分割、Window表示もそれぞれ、このライブラリのフィルターです。)

左上から順に、

  1. キャプチャ画像(の鏡像)
  2. 1をグレイスケールに変換
  3. 2に対して、ヒストグラム均一化とガウシアンブラーを適用
  4. 3の過去18000フレーム分を平均
  5. 3と4の差分(絶対値差分)
  6. 5の二値化

このあと、6の二値化画像に対して、膨張(dilate)と縮小(erode)を適当に繰り返してから元画像(1の画像)をマスクすれば背景と前景に分離できそうです。

処理は全てsample.xml(↓)に記述しており、参考として下に掲載しているC++のコード(capture.cpp)で読み込んで処理させています。 このためビルド無しで処理を変更可能です。(XMLを読み込む処理は複数のフィルターをまとめるImgProcSetの機能です)

sample.xml - 背景除去するXML

<cvImagePipeline name="testProcessor">
  <Processor class="VideoCapture" name="cap">
    <Property name="deviceIndex" value="0"/>
  </Processor>
  <Processor class="Flipper" name="fripHoriz">
    <Property name="flipDir" value="1"/>
  </Processor>
  <Processor class="ImagePoint" name="raw"/>
  <Processor class="ColorConverter" name="grayscale"/>
  <Processor class="EqualizeHist" name="equalizeHist"/>
  <Processor class="GaussianBlur" name="blur"/>
  <Processor class="DepthTo32F" name="depth32F"/>
  <Processor class="ImagePoint" name="pp"/>
  <Processor class="RunningAvg" name="background"> 
    <Property name="averageCount" value="18000"/>
  </Processor>
  <Processor class="AbsDiff" name="diff" autoBind="false">
    <Input to="src1" from="pp"/>
    <Input to="src2" from="background"/>
  </Processor>
  <Processor class="Convert" name="to8UC">
    <Property name="rtype" value="0"/>
    <Property name="alpha" value="255"/>
    <Property name="beta" value="0"/>
  </Processor>
  <Processor class="Threshold" name="binary">
    <Property name="type" value="CV_THRESH_BINARY"/>
        <!--
   CV_THRESH_BINARY
   CV_THRESH_BINARY_INV
   CV_THRESH_TRUNC
   CV_THRESH_TOZERO
   CV_THRESH_TOZERO_INV
   -->
    <Property name="otsu" value="1"/>
    <Property name="thresh" value="50"/>
    <Property name="maxval" value="255"/>
  </Processor>

  <Processor class="FitInGrid" name="integratedImage" autoBind="false">
    <Property name="width" value="960"/>
    <Property name="height" value="480"/>
    <Property name="cols" value="3"/>
    <Property name="rows" value="2"/>
    <Property name="interleave" value="0"/>
    <Input to="0" from="raw"/>
    <Input to="1" from="grayscale"/>
    <Input to="2" from="blur"/>
    <Input to="3" from="background"/>
    <Input to="4" from="diff"/>
    <Input to="5" from="binary"/>
  </Processor>
  <Processor class="ImageWindow" name="window">
    <Property name="windowName" value="cvImagePipeline"/>
    <Property name="showFPS" value="1"/>
  </Processor>
</cvImagePipeline>

capture.cpp - XMLを処理するプログラム

#include "stdafx.h"
#if defined(_MSC_VER)
#include <windows.h>
#else
#include <unistd.h>
#define Sleep(millisec) usleep(millisec * 1000)
#endif
#include <opencv2/opencv.hpp>
#include "cvImagePipeline.h"

using namespace cvImagePipeline;
using namespace cvImagePipeline::Filter;

#if defined(_MSC_VER)
int _tmain(int argc, _TCHAR* argv[])
#else
int main(int argc, char* argv[])
#endif
{
    cvInitSystem(argc, argv);
    ImgProcSet processors;
    std::string xml_filename("sample.xml");
    if (argc > 1) {
        xml_filename = argv[1];
    }
    if (!processors.loadXml(xml_filename)) {
        std::cerr << "ファイル読み込み失敗 ファイル名:"
            << xml_filename << std::endl;
        return -1;
    }
    Sleep(2000);
    while(true) {
        processors.execute();
        int ch = cvWaitKeyEx(1);
        if (ch == '\x1b') {
            fprintf(stderr, "exit.\n");
            break;
        }
    }
    cvDestroyAllWindows();
    return 0;
}

リポジトリ

上の例も含めて、その他詳細は下記リポジトリのREADMEを参照してください。

実を言うと、このリポジトリ、1年以上放置しているんですねえ。 しかし今でも Clone してくださる方が月に数人いらっしゃるようです。 READMEが古くなっていて申し訳ないですから、これを機にキチンとメンテしようと思ってはいますが・・・。

github.com

そういや以下の記事でもシレッと使っていますねw

takamints.hatenablog.jp

takamints.hatenablog.jp

今後の課題

今考えつく今後の課題は以下の様なことです。放置している場合ではないなあ。

  • OpenCV 3.0以上での動作確認。
  • Windows 10 での動作確認。
  • 基本フィルタと他のサンプルフィルタの分離。
  • 追加実装のしやすさを追求。
  • XMLXAML的文法に変更(記述量を少なくできそう)。
  • XMLのビジュアルな編集。
  • デバッグ機能の充実。
  • 実行中のフィルタのバイパスやパラメータの変更機能。
  • Pythonから利用できるインタフェース。C++はやっぱり敷居が高いかも。
  • 妙に凝った変な名称のフィルタを改名するw