2020/09/26

xtensor

C++でもnumpyっぽく使えるxtensorを試してみます。

インストール

まずは、インストール。次のスクリプトを実行します。

#!/bin/bash
git clone https://github.com/xtensor-stack/xtensor.git
git clone https://github.com/xtensor-stack/xtl.git
git clone https://github.com/xtensor-stack/xsimd.git
git clone https://github.com/xtensor-stack/xtensor-blas.git

INSTALL_PATH=$(pwd)/root
for x in xtl xsimd xtensor xtensor-blas
do
    echo "-------- $x ----------"
    cd $x
    mkdir -p build
    cd build
    cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PATH ..
    make install
    cd ../..
done

行列積の計算時間

行列積の計算時間を測定してみます。測定する処理を書いたC++のコードは次の通り。10000x10000の2つの行列の積を計算し、その後、平均値を計算します。これを5回繰り返します。なお、平均値は10000になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include "xtensor.hpp"
#include "xtensor-blas/xlinalg.hpp"

void dot(void){
  xt::xarray<float> a = xt::ones<float>({10000, 10000});
  xt::xarray<float> b = xt::ones<float>({10000, 10000});
  for(int i=0; i<5; ++i){
    auto d = xt::linalg::dot(a, b);
    auto c = xt::average(d);
    std::cout << c << std::endl;
  }
}

int main(void){
  dot();
  return 0;
}
このファイルをdot.cppとして保存し、次のようなCMakeLists.txtを作成します。xtensor_pathの値は適切なものに書き換えてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.10)
project(xtexample VERSION 1.0)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_definitions(-DHAVE_CBLAS=1)
find_package(BLAS REQUIRED)
find_package(LAPACK REQUIRED)

add_executable(xtexample dot.cpp)
set(XTENSOR_USE_XSIMD ON)
set(xtensor_path "/path/to/xtensor/root")
set(xtensor_DIR "${xtensor_path}/lib/cmake/xtensor")
set(xtl_DIR "${xtensor_path}/lib/cmake/xtl")
set(xsimd_DIR "${xtensor_path}/lib/cmake/xsimd")
find_package(xtensor REQUIRED)
target_include_directories(xtexample PUBLIC ${xtensor_INCLUDE_DIRS})
target_link_libraries(xtexample PUBLIC xtensor xtensor xtensor::optimize xtensor::use_xsimd
                      ${BLAS_LIBRARIES} ${LAPACK_LIBRARIES})
ビルドします。
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ make
処理時間を測定します。
$ for _ in {1..4}; do time ./xtexample; done
結果は次の通りです。4コア8スレッドのCPUで測定しています。
Trial #realusersys
10m43.805s5m17.723s0m23.991s
20m46.952s5m44.626s0m21.066s
30m49.060s5m59.719s0m22.163s
40m50.819s6m9.617s0m25.812s

Python numpyとの比較

比較のため、Pythonのnumpyで同じことをしてみます。測定コードは以下の通り。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np

def dot():
    a = np.ones((10000, 10000), dtype=np.float32)
    b = np.ones((10000, 10000), dtype=np.float32)
    for i in range(5):
        d = np.dot(a, b)
        c = np.average(d)
        print(c)

dot()
C++の場合と同様に実行します。
for _ in {1..4}; do time python3 comp.py; done
結果は次の通り。
Trial #realusersys
10m44.816s5m29.361s0m17.726s
20m49.529s6m5.012s0m21.217s
30m49.188s5m58.570s0m24.681s
40m50.497s6m11.620s0m22.015s
C++と同じくらいの結果となりました。

まとめ

C++でnumpyのように行列計算を使いたい場合は、xtensorを使えばよさそうです。 処理速度に関しても、少なくとも行列積に関してはnumpyに比べて遅いわけではないようです。

2020/07/04

カメラで撮影した紙の画像の濃淡除去

目的


皺があったり光を一様に当てられない環境で紙をカメラで撮影すると、明るい部分と暗い部分ができてしまいます。
画像を二値化したいとき、このような明るさの変化が二値化の障害になることがあります。 そこで、これを緩和する方法を試してみます。 全体的な明度差のある画像に対応できていないOCRを使うときの前処理のひとつとして使えるかもしれません。

手法


日本の特許(登録番号3909604)を使ってみます。

畳み込み演算でぼかした画像のピクセルで元のピクセルの値を割った値を元に画像を再構成することで、細かい構造を残しながら全体的な濃淡を除去します。

詳細は https://www.inpit.go.jp/blob/katsuyo/pdf/business/19t1-2.pdf または、特許公報を参照してください。

ソースコード


gray.pngを読み込んで処理したあと、mod.pngに書き込みます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
import cv2

def flatten(img, kernel_size=77):
    # Make a kernel
    half = kernel_size//2
    k = np.array(list(range(1,half+1))+[half+1]+list(range(half, 0, -1)))
    ks = [k]
    for i in range(2, half+2, 1):
        ks.append(k*i)
    for i in range(half, 0, -1):
        ks.append(k*i)
    k = np.stack(ks)
    k = np.array(k, dtype=np.float32)/np.sum(k)

    # Calculate uneven coefficients (JPP3909604)
    ave = cv2.filter2D(img, -1, k).astype(np.float32)
    a = img.astype(np.float32)/ave

    # Make a flatten image
    a = (a-1)*768+255
    a[a>255] = 255
    a[a<0] = 0
    return a.astype(np.uint8)

if __name__ == '__main__':
    img = cv2.imread('gray.png')
    img = flatten(img)
    cv2.imwrite('mod.png', img)


処理例を以下に示します。

処理前の画像

処理後の画像
文字周りの明度はある程度一定になっていることがわかります。

2020/06/14

よくある文書フォーマット

ただのメモです。よくあるのはこういう形式。灰色の文字は書いてはいけませんw。

20xx年x月x日
あいうえお様(受取人名)
かきくけこ(差出人名)

(件名)さしすせそについて

 平素よりほにゃららほにゃららお礼申し上げます。
 かきくけこは・・・ご案内いたします。


1.あああ

2.いいい

3.ううう

以上


2020/06/13

世界各国のコレステロール値

世界各国でのコレステロール値の変化。詳細は[1]を参照。

non-HDL


1980年と2018年の、年齢標準化されたHDLでないコレステロールの平均値を表したものが下図([1]のFig.3)。赤いほど値が大きい。
a: 1980年女性, b: 2018年女性, c: 1980年男性, d: 2018年男性

同図のキャプションに、"One mmol/l is equivalent to 38.61 mg/dl." とあるので、カラーバー中央の黄色で3.5mmol/l x 38.61 ≃ 135.1 mg/dl。 日本は、女性の値が低下している一方、男性の値は色で見る限りでは変化していない。

HDL


1980年と2018年の、年齢標準化されたHDLコレステロールの平均値を表したものが下図([1]のExtended Data Fig. 6)。

カラーバーの濃緑色付近の1.4 mmol/l x 38.61 ≃ 54.1 mg/dlである。 「HDL-コレステロール値が低いと診断された方へ」によると、 HDLの基準値は1.036 mmol/l以上であるので、赤色は平均値が基準以下を表している。日本は男女とも上昇している。

引用元文献


[1] Taddei, C., Zhou, B., Bixby, H. et al. Repositioning of the global epicentre of non-optimal cholesterol. Nature 582, 73–77 (2020). https://doi.org/10.1038/s41586-020-2338-1 (Open Access, CC BY 4.0)

2020/04/19

Emacsで入力補完 その2

はじめに


先日の記事ではEmacsのcompany-modeの設定をしましたが、 CまたはC++を使う場合のプロジェクト内のヘッダファイルを検索対象にするために、ひと手間必要でした。

ここではそれを自動化するための設定をしてみます。

設定


プロジェクトルートを見つけるため、最初にProjectileをインストールします。
M-x package-install [Enter] projectile [Enter]
次に、~/.emacs.d/init.elに以下の内容を記載します。
(require 'company)
(global-company-mode t)
(define-key global-map (kbd "C-f") 'company-complete)
(setq company-backends '((company-clang company-dabbrev-code company-gtags)))

(require 'projectile)
(define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
(projectile-mode +1)

;; Return a project root if it is found else a current directory                                                                                                  
(defun pproot () (let ((d (projectile-project-root))) (unless d (setq d ".")) d))

;; Find all include paths and set them to clang include path                                                                                                    
(defun set-company-clang-include-path ()
  (interactive)
  (setq company-clang-arguments
        (mapcar (lambda (x) (concat "-I" x)) ;; Make clang options to set include path                                                                          
                (seq-uniq (mapcar (lambda (x) (replace-regexp-in-string "/[^\/]*$" "" x)) ;; Extract directories                                                
                                  (directory-files-recursively (pproot) ".*\.\\(h\\|hpp\\)$")))))) ;; Find all header files
(defun set-company-clang-include-path-if-empty ()
  (unless company-clang-arguments (set-company-clang-include-path)))
(add-hook 'c++-mode-hook 'set-company-clang-include-path-if-empty)
(add-hook 'c-mode-hook 'set-company-clang-include-path-if-empty)
プロジェクトルート以下の.h/.hppをすべて検索するため、大きめのプロジェクトでは、 c-modeまたはc++-modeでファイルを開くと最初は時間がかかるかもしれません。

company-clang-argumentsを更新する場合は、M-x set-company-clang-include-pathを実行します。

2020/04/12

Emacsで入力補完

はじめに


EmacsにIntellisenseっぽいものを導入してみます。

準備


Packageを使う準備ができていない場合は、
https://melpa.org/#/getting-started
に従って準備します。

設定ができたらemacsを立ち上げて、 M-x package-list-packages を実行します。

パッケージの一覧が表示されますので、companyの行へ移動します。なければ r を押してください。リストが更新されます。

companyの行へ移動できたら i を押します。すると、その行に I が表示され、インストール準備が完了した状態になります。

最後に x を押すとインストールしてよいかの確認の後、インストールされます。

使い方


emacsを立ち上げて、 M-x global-company-mode を実行すれば有効になります。

C++のファイルを開いているときにcompany-modeを有効にしようとして、もし、

Company backend ’company-clang’ could not be initialized:
Company found no clang executable
というエラーがでた場合は、clangがインストールされていないということなので、aptなどでclangをインストールします。

この状態でも使えるのですが、emacsを起動するたびにglobal-company-modeを有効にしなければならないので、 ~/.emacs.d/init.el に次の内容を追加しておきます。

(global-company-mode t)
(global-set-key (kbd "C-f") 'company-complete)
このようにすると、Ctrl+f で自動補完が動作するようになります。 カーソル移動にCtrl+fを使う人は別のキーに割り当てましょう。

C++のプロジェクト内で定義した関数等の補完


C++では、global-company-modeを有効にしただけでは標準の関数等に対してしか補完は行われません。 今まさに作っている最中のプロジェクト内のファイルに対する補完が効かないということです。これは、補完のために呼び出されるclangが読み込むべきファイルのパスを知らないためです。

そこで、プロジェクトのルートに次のような内容を記載した .dir-locals.el という名前のファイルを作成します。

((nil . ((company-clang-arguments . ("-I/path/to/include/"
                                     "-I/path/to/2nd/include/")))))
これで、指定したパスのファイル内で定義されている関数等は補完の対象になります。サブディレクトリを再帰的に見てくれるわけではないので注意が必要です。

なお、このファイルを作成すると、emacs起動時に

The local variables list in /path/to/include/ contains values that may not be safe (*).
と聞かれるようになってしまいます。設定値に文字列が入っていると聞いてくるようです。許可すればいいだけですが、ちょっと手間ですね。

まとめ


使えることは使えるのですが、プロジェクトごとにファイルを準備しないといけないため、 常用するにはもうひと工夫必要になりそうです。

参考


company の説明
http://company-mode.github.io/
http://emacs.rubikitch.com/company/
https://qiita.com/syohex/items/8d21d7422f14e9b53b17

emacs設定いろいろ
http://tuhdo.github.io/c-ide.html

clangが見つからないと言われたら
https://emacs.stackexchange.com/questions/19306/how-do-i-get-company-mode-to-recognize-clang

.dir-locals.el の説明
https://www.emacswiki.org/emacs/DirectoryVariables

bashのキーボード・ショートカット一覧
https://ss64.com/bash/syntax-keyboard.html

2020/03/01

For 〜ingとTo infinitiveの違い

すぐに忘れてしまうので、メモ。

For 〜ing : 機能か理由を表す場合

To inifinitive : 目的か意図を表す場合

例を含む詳細は以下のページで確認できます。
https://ell.stackexchange.com/questions/24553/how-can-i-decide-when-to-use-for-ing-or-to-infinitive-in-a-sentence
https://dictionary.cambridge.org/us/grammar/british-grammar/for-ing

2020/02/16

インフルエンザの報告数の推移

報告数


新型コロナウイルスの話があふれていますが、毎年流行っているインフルエンザの報告数について調べてみました。

指定届出機関からのインフルエンザの週ごとの報告総数です。 データは https://www.niid.go.jp/niid/ja/data.htmlから入手できます。

今年は2018年や2019年に比べて3分の1くらいになっています。2019年12月頃までは2018年に比べて多かったにもかかわらず、年が明けると例年並みに減り(1週目の値)、そこから報告数が拡大していません。どうしてなのでしょうね。

死亡率


ついでに、インフルエンザによる死亡率についても調べてみました。 米国のデータでは、 https://www.ncc.go.jp/jp/ncce/division/safety_management/about/kansen/020/010/20181002160441.html から引用すると、
年齢10万人あたりの死亡者数(米国)
25歳-34歳0.1
65歳-74歳2.2
75歳-84歳8.7
85歳以上43.3
とのこと。

リンクされている元データをたどってみると、 https://www.cdc.gov/nchs/data/nvsr/nvsr68/nvsr68_09-508.pdf のTable 5がそれっぽいので、その値をみてみると、

年齢10万人あたりの死亡者数(米国) 2017年
25歳-34歳0.9
65歳-74歳29.6
75歳-84歳93.8
85歳以上375.3
何か桁が1つ違うような。どうしてだろう。

それはともかく、10万人あたりだと実感がわかないので、%にしてみると、

年齢死亡率(%) (米国) 2017年
25歳-34歳0.0009
65歳-74歳0.0296
75歳-84歳0.0938
85歳以上0.3753
となりました。

日本での死亡率は https://www.hosp.med.osaka-u.ac.jp/home/hp-infect/file/ictmon/ictmon162.pdf に掲載されている図3から数値をざっくり読み取ったところ、

年齢死亡率(%) (日本)
2009年8月3日〜2009年12月13日
30歳-39歳0.001
60歳-69歳0.016
70歳以上0.032

でした。年齢の区分が異なるのでなんともいえませんが、日米ともだいたい同じくらいなのかな。

インフルエンザとは?


インフルエンザの説明は
https://www.niid.go.jp/niid/ja/kansennohanashi/219-about-flu.html
あたりが参考になります。

2020/02/08

C++のビルド済みバイナリをpipでインストール

目的

C++ と cmake で作った実行ファイルを、pipでインストールできる形式にします。 加えて、pythonのライブラリも添付します。

なお、PyPIへのアップロード・配布は考慮していません。

作り方

pythonの準備

skbuildを使うので、pip install scikit-build でインストールしておきます。

python 3.8.1 で動作確認しています。

pipでインストールするC++のプログラムの準備

練習用C++プログラムを準備します。既存の、C++ & cmakeでビルドするプログラムがあれば、ここはスキップします。

まず、練習用C++プログラムの本体です。

[aaa.cpp]

1
2
3
4
5
6
7
8
9
#include <iostream>

int main(int argc, char *argv[]){
  std::cout << "aaa bbb ccc" << std::endl;
  for(int i=0; i<argc; ++i){
    std::cout << argv[i] << std::endl;
  }
  return 0;
}
文字列を表示するだけのプログラムです。

これをビルドするCMakeLists.txtを作成します。

[CMakeLists.txt]

1
2
3
4
cmake_minimum_required(VERSION 3.11.0)
project(projAAA)
add_executable(aaa aaa.cpp)
install(TARGETS aaa RUNTIME DESTINATION .)
ここでは aaa という名前のコマンドを作成しています。C++やcmakeの説明は省略します。

setup.py

配布パッケージを作成するためのPythonスクリプト setup.py を作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from skbuild import setup
setup(name="bdist-template", # パッケージの名前。pipでインストール後、pip list で表示される名前
      version="0.0.0",
      author="Your NAME",
      author_email="your@name.com",
      packages=["bdist_example"],  # importするときに使う名前。nameと一致してなくても良い。
                                   # 同じ方がユーザーには親切かも。
      cmake_install_dir="bdist_example/bin", # 最初のディレクトリ名はpackagesで指定した名前と同じにする
      entry_points={
          "console_scripts": [
              "cmdaaa=bdist_example:python_function_aaa"
          ]
          # cmdaaa がpip install後にシェルから使えるコマンド名になる
          # =と:の間は、packagesで指定した名前と同じにする
          # :の右側は後ほど説明
      },
      url="http://yourname.abc",
      download_url="https://yourname.abc/download",
      description="Test package", # このパッケージの短い説明文
      long_description="Test package !!!", # このパッケージの詳細な説明文
      classifiers=[
          "Programming Language :: C++",
          'Programming Language :: Python'
      ],
      license="???" # ライセンス名を記載(GPLとかApacheとか)
)

__init__.py

pipでインストールする場合、C++でビルドした実行ファイルをそのまま実行するのではなく、Pythonを一旦経由するので、 そのためのスクリプトを準備します。

まず、 setup.py のpackagesで指定した名前(ここではbdist_example)のディレクトリを作成します。 そこに次のような内容を含む __init__.py を作成します。

[bdist_example/__init__.py]

1
2
3
4
5
6
7
8
import os
import subprocess
import sys

CMAKE_BIN_DIR = os.path.join(os.path.dirname(__file__), "bin")

def python_function_aaa():
    raise SystemExit(subprocess.call([os.path.join(CMAKE_BIN_DIR, "aaa")] + sys.argv[1:]))
"aaa"の部分を CMakeLists.txt で書いたプログラム名と一致させます。

python_function_aaa は setup.py の console_scripts の部分の : の右側部分に書いた名前と一致させます。

同梱するスクリプト

次のスクリプトも同梱するようにします。バイナリのみの配布であれば、ここはスキップします。

[bdist_example/aaalib.py]

1
2
def print_aaaaa():
    print("aaaaa")

.whlファイルの作成

ここまでそろうとパッケージを作ることができます。

$ python setup.py bdist_wheel
を実行すると、distディレクトリの中に.whlファイルが作成されます。

インストール

作成した.whlを次のようにしてインストールします。

$ pip install dist/bdist_template-0.0.0-cp38-cp38-linux_x86_64.whl 
Processing ./dist/bdist_template-0.0.0-cp38-cp38-linux_x86_64.whl
Installing collected packages: bdist-template
Successfully installed bdist-template-0.0.0

インストールされたことの確認は、

$ pip list
Package        Version
-------------- -------
bdist-template 0.0.0  
packaging      20.1   
pip            20.0.2 
pyparsing      2.4.6  
scikit-build   0.10.0 
setuptools     41.2.0 
six            1.14.0 
wheel          0.34.2 
でできます。bdist-templateがインストールされていることがわかります。

動作確認

先程作成したコマンドが呼び出せることを確認します。

$ cmdaaa x y z
aaa bbb ccc
/home/user/.pyenv/versions/3.8.1/envs/py38/lib/python3.8/site-packages/bdist_example/bin/aaa
x
y
z
C++でビルドしたコマンドそのものは aaa でしたが、setup.pyで書いたコマンド名 cmdaaa が有効になっていることが確認できました。

同梱したスクリプトをpythonから実行してみます。

>>> import bdist_example.aaalib
>>> bdist_example.aaalib.print_aaaaa()
aaaaa

おまけ1

データを同梱したい場合は、次のようなコードを setup.py のsetup関数に渡す引数に追加します。

setup(...
      include_package_data=True,
      package_data={
          "bdist_example": ["data/*.dat"],
      }
)
ここでは、bdist_example/data/*.dat にデータがあると仮定しています。

おまけ2

.whlのファイル名でインストールできるpythonのバージョンが制限されるので、

$ mv bdist_template-0.0.0-{cp38-cp38,py3-none}-linux_x86_64.whl
のようにリネームしておくと、Python3ならインストールできるようになります。

ファイル名での制限を外すと、動作しないバージョンのPythonにインストールできてしまうので、代わりに、

setup(...
      python_requires='~=3.6'
)
のように指定しておきます。バージョン指定の書き方は
https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
が参考になります。

まとめ

skbuild により、 setup.py と __init__.py を準備するだけで .whl ファイルを作ることができました。

参考文献

scikit-build (skbuild) のドキュメント
https://scikit-build.readthedocs.io/en/latest/usage.html#basic-usage

cmake-python-distributions のコード
https://github.com/scikit-build/cmake-python-distributions

プロジェクトのパッケージングと配布
https://python-packaging-user-guide-ja.readthedocs.io/ja/latest/distributing.html
https://packaging.python.org/guides/distributing-packages-using-setuptools/

2019/11/10

sox equalizer

目的


soxのequalizerのヘルプに書かれている [q|o|h|k] の意味がわからないので、調べてみます。
$ sox --help-effect equalizer
sox:      SoX v14.4.2

Effect usage:

equalizer frequency width[q|o|h|k] gain
幅の単位であることは読み取れます。

調査に使ったコード


調査には https://git.code.sf.net/p/sox/code を利用しました。 コミットのハッシュ値は 2f6b3fec2dddfbb869a9f7de3110c9aaa31517c9 (Date: Wed Aug 1 23:45:59 2018 -0400) でした。

結果


hはHz、qはHzの逆数に操作する周波数を掛けた値であることがわかりました。また、oはオクターブっぽく、kはよくわからないこともわかりました。

記載箇所


equalizerは src/biquads.c に記述されています。

オプションの解析部分を追っていくと、

q = width_Q
o = width_bw_oct
h = width_bw_Hz
k = width_bw_kHz
であることがわかります。

このファイル内に、これらを使っている箇所があります。

static int start(sox_effect_t * effp)
{
  ...
  if (p->width) switch (p->width_type) {
    case width_slope:
      alpha = sin(w0)/2 * sqrt((A + 1/A)*(1/p->width - 1) + 2);
      break;

    case width_Q:
      alpha = sin(w0)/(2*p->width);
      break;

    case width_bw_oct:
      alpha = sin(w0)*sinh(log(2.)/2 * p->width * w0/sin(w0));
      break;

    case width_bw_Hz:
      alpha = sin(w0)/(2*p->fc/p->width);
      break;

    case width_bw_kHz: assert(0); /* Shouldn't get here */

    case width_bw_old:
      alpha = tan(M_PI * p->width / effp->in_signal.rate);
      break;
  }
  ...
width_Qはwidth_bw_Hzとは逆に動いているので、qを大きくすると幅が狭くなるようです。

width_bw_kHzについては、assert(0)では止まらないので、はwidth_bw_old扱いになるようです。 全然kHzではありませんね。

参考までに、pの型はbiquad_tで、定義は、src/biquad.h より、

typedef struct {
  double gain;             /* For EQ filters */
  double fc;               /* Centre/corner/cutoff frequency */
  double width;            /* Filter width; interpreted as per width_type */
  width_t width_type;

  filter_t filter_type;

  double b0, b1, b2;       /* Filter coefficients */
  double a0, a1, a2;       /* Filter coefficients */

  sox_sample_t i1, i2;     /* Filter memory */
  double      o1, o2;      /* Filter memory */
} biquad_t;
です。

2019/08/31

Debian10のPCのルータ化

目的


Debian10 (Buster) をインストールしたPCをルータにします。

インターネット側のネットワークインターフェイスenxと、内部ネットワーク側のネットワークインターフェイスeniがあるとして説明します。

[内部PC1]┬(eni)[ルータ化するPC](enx)─[インターネット]
[内部PC2]┘
なお、試行錯誤したあとにまとめているため、記載通りでは動かない可能性があります。

おおまかな手順


おおまかな作業手順は次のとおりです。
  • IPアドレスの固定
  • isc-dhcp-server (DHCPサーバ)のインストールと設定
  • nftables (NAT用)のインストールと設定
  • bind9 (DNSサーバ)のインストールと設定
以下の作業ではすべてルートで行うか、sudoで実行してください。

IPアドレスの固定


Debian10ではNetworkManagerがデフォルトで動作しています。そのため、nmcliを使って、eniのIPアドレスの固定します。 残念なことにGUIを使った設定では、いずれか1つのネットワークインターフェイスしか有効にできないようです。

nmcliの解説はhttps://go-journey.club/archives/4124が参考になります。

有線LANの場合、作業の開始前にLANケーブルを接続しておくことをお薦めします。 接続がないとSTATEに利用不可と表示され、設定した結果を確認できなくなります。

最初に、ネットワークインターフェイスをNetworkManagerから見えるようにします。

$ nmcli connection add type ethernet ifname eni con-name eni
次に、eniのIPアドレスを指定します。
$ nmcli connection modify eni ipv4.method manual ipv4.addresses 10.0.0.1/27
しばらくすると、IPアドレスが固定され、ネットワーク接続が完了します。

ネットワークの状態は

$ nmcli device
で確認できます。

DHCPサーバの設定


isc-dhcp-serverをインストールします。
$ apt install isc-dhcp-server
isc-dhcl-serverの設定ファイルは /etc/dhcp/dhcpd.conf にあります。

そこに次の内容を追記します。

subnet 10.0.0.0 netmask 255.255.255.224 {
  range 10.0.0.10 10.0.0.20;
  option routers 10.0.0.1;
  option domain-name-servers 10.0.0.1;
}
IPアドレスは適宜変更してください。この設定で配布されるIPアドレスは、10.0.0.10〜10.0.0.20の範囲です。

最後に、DHCPサービスを再起動します。

$ systemctl restart isc-dhcp-server
これで内部PCにIPアドレスが配布されます。

NATの設定


ルータとして動作するようにするために、まず、 /etc/sysctl.conf に
  net.ipv4.ip_forward=1
を追記します。コメントアウトされた状態で記載されているので、コメントを外すだけです。

この設定を次のコマンドを実行することで有効にします。

$ sysctl --system
有効になっているかは
$ cat /proc/sys/net/ipv4/ip_forward
で確認できます。出力が1になっていれば、OKです。

次に、NATを有効にします。NATはファイアウォールの機能の一部ですので、Debian10から標準となったnftablesを用います。

$ apt install nftables
でインストールし、/etc/nftables.conf に次の内容を追加します。
table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
  }
  chain postrouting {
    type nat hook postrouting priority 100;
    oifname "enx" masquerade
  }
}
追記できればサービスを有効化し、実行します。
$ systemctl enable nftables.service
$ systemctl restart nftables
クライアントから8.8.8.8にpingが通るようになっていれば成功です。

DNSサーバの設定


BIND9をインストールするだけです。
$ apt install bind9
https://www.clear-code.com/blog/2010/11/19.htmlが参考になります。

もし、クライアントのブラウザからドメイン名を使ったアクセスができない場合は、

$ systemctl restart isc-dhcp-server
で、DHCPサービスを再起動してみてください。

以上で、ルータが完成です。セキュリティー関連の設定は別途実施してください。

2019/08/25

Debian10でのデスクトップ環境の変更

Debian10は、インストール時にデスクトップ環境を複数インストールすることができます。

例えば、GNOMEとCinnamonの2つをインストールすると、ログイン時にどちらの環境でログインするかを選択することができます。 この2つをインストールしておくと、デフォルトではGNOMEが選択されるようになっています。

デスクトップ環境をGNOMEからCinnamonに変更するには、ログイン画面のパスワード欄の下にある、小さいギアのアイコンのボタンを押します。 すると、選択できるデスクトップ環境の一覧が表示されるので、使いたい環境を選択します。

その後、ログインすると、選択した環境で作業できるようになります。

ボタンの場所は記事の図を参考にしてください。
https://computingforgeeks.com/how-to-install-cinnamon-desktop-en\ vironment-on-debian/
具体的には、 install-cinnamon-deb10-03.pngです。

Debian10でWacomタブレットを使う

現象


Debian10を標準でインストールし、Wacomのタブレット(CTH-480)をつなげ、ペンで操作していると、マウスカーソルが頻繁に消失します。 また、タブレット操作とペン操作のそれぞれに対してマウスカーソルが表示されるという症状も出ていました。

原因


Waylandがタブレットを正しく処理できていないことが原因のようです。

解決策


/etc/gdm3/daemon.conf でコメントアウトされている次の行のコメントを外して有効にします。
WaylandEnable=false
その後、再起動すると、Waylandの代わりに従来のXが使われるようになります。

Xは正しくペンタブレットを処理できるようで、マウスカーソルが消えたり2つになったりする現象はなくなりました。

WaylandをOFFにする方法は、
https://linuxfan.info/ubuntu-gdm-wayland-false
を参考にしました。

2019/07/27

電気いらずの脱塩装置

太陽光だけで海水を真水にできる装置が発表されました。

https://www.monash.edu/news/articles/water-solutions-without-a-grain-of-salt
https://www.newsweekjapan.jp/stories/world/2019/07/post-12625.php

円盤状の濾紙の中央に水を吸い上げて、太陽光で蒸発させます。
これを集めれば真水になりますね。

水に溶けていた塩は円盤の外側へと排出されます。

もこもこ塩が湧き出てくる動画が面白い。
https://youtu.be/OGZVhcQ33MQ

2019/05/27

J-PlatPat が更新

J-PlatPatが更新されて、いい感じに使いにくくなったので、とりあえずの検索をするときの使い方をメモしておきます。

投稿日時点の使い方なので、今後使い方が変わっていく可能性はあります。
(そもそもずいぶん前に更新されていたのかもしれません)

ざっくり調べたいときは、全文検索かつキーワードをANDでつなげたいことが多いです。
Googleの検索と同じですね。

選択入力


「選択入力」を使うときは、「検索項目」を「全文」に設定して、キーワードに1単語ずつ入力していきます。1単語ずつ!、です。複数単語を入れるとORになります。
残念なことに指定したい単語の数が多いと、この作業はとても手間がかかるので、「論理式入力」を使ったほうが簡単です。

論理式入力


論理式入力で全文検索かつANDで単語をつなげるには、例えば
機械学習/TX*分散処理/TX*プロトコル/TX
または、
(機械学習*分散処理*プロトコル)/TX
のように指定します。*がAND式で、/TXが全文を対象にする式(構造タグ)です。

使える演算子について


ヘルプには記載されているのですが、見たら分かるであろう画面の使い方が記載されている部分が多く、見つけるのに時間がかかったので、ここにURLを貼っておきます。

[条件式一覧]
https://www.j-platpat.inpit.go.jp/help/ja/p01/arithmetic.html
の「使用できる演算子について」を開くと読めます。簡単に書いておくと、

* AND
+ OR
- NOT
[ ] 演算順位の変更(+と-を*より強くするときなどに使う)
( ) /以降をまとめる

例:

('Liquor'*['Wine'-'Japanese beer'])/TX

[構造タグ一覧]
https://www.j-platpat.inpit.go.jp/help/ja/p01/p0101.html#9994

使用頻度が高そうなものをピックアップしておきます。
TX 全文
AB 要約/抄録
CL 請求の範囲
FI FI
FT Fターム
IP IPC

でしょうか。

文献内検索


さて、そうして見つけた文献を表示して、ブラウザの検索機能で当該箇所を探そうとすると、全文が表示されていないため、全文検索ができません。そこで、「要約」「請求の範囲」「詳細な説明」を1つずつ開いておきます。幸いにも、次の文献を表示したときにまた当該項目が閉じられてしまうことはないので、多少はましでしょうか。サーバーの負荷を下げたい一心で設計したんでしょう。手間がかかってしかたがないですね。


また、文献サイズが大きいと、一度に表示できずに分割されてしまいます。
そのため、ブラウザの検索機能を使って全文を検索できません。
そういった場合は、仕方がないので、
- pdf出力をする
- Google patentで検索する
にしましょう。

そもそも最初からGoogle patentでいいのかもしれませんね。

2019/02/03

wavの簡易表示

端末でwavファイルの中身を簡易表示するスクリプトを作ってみました。
https://bitbucket.org/bluewidz/asciiwave/src/default/aw
soxが必要です。

実行例です。

$ ./aw a.wav | head -n5
0.100s  18893   12375   18893   12375   18893   12375
0.200s  10841   6235    10841   6235    10841   6235
0.300s  8328    4031    8328    4031    8328    4031
0.400s  7479    3369    7479    3369    7479    3369
0.500s  6779    3056    6779    3056    6779    3056
0.100sの列は、0s-0.1sの間のサンプル値の絶対値の最大値を表示しています。
列はチャンネルを表しています。一番左が1チャネル目です。6列あるので、このファイルは6チャネル分のデータが含まれていることになります。

スケールを変更する場合は、1行にまとめるサンプル数を直接指定します。
$ ./aw -s 480 a.wav | head -n5
0.010s  0       0       0       0       0       0
0.020s  0       0       0       0       0       0
0.030s  0       0       0       0       0       0
0.040s  0       0       0       0       0       0
0.050s  39      25      39      25      39      25

2019/02/02

ZeroMQでソケット通信

はじめに


Python3 + ZeroMQでソケット通信を試してみます。

PythonでZeroMQを使うためのpyzmqのドキュメントは、
https://pyzmq.readthedocs.io/en/latest/
https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/index.html
が、ZeroMQそのものの説明は
http://zguide.zeromq.org/page:all
が参考になります。

次の4パターン

  • Pair (Exclusive pair)
  • Client/Server (Request-reply)
  • Publish/Subscribe (Pub-sub)
  • Push/Pull (Pipeline)
が使えるようです。今回は、Publish/Subscribeで試します。せっかくなので、送受信にかかる時間も測定してみます。

コード


Publisher側のコードです。socket.send_pyobj でpythonのオブジェクトをそのまま渡すだけです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import zmq
import time
import numpy as np

context = zmq.Context.instance()
socket = context.socket(zmq.PUB)
socket.bind("ipc://aaa")

w = np.ones(160) # numpy array to be sent
while True:
    t = time.perf_counter()
    socket.send_pyobj([t, w])
    print(t)
    time.sleep(1)

次に、Subscriber側のコードです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import zmq
import time

context = zmq.Context.instance()
socket = context.socket(zmq.SUB)
socket.connect("ipc://aaa")
socket.subscribe('') # Recieve all topics

while True:
    start, w = socket.recv_pyobj()
    end = time.perf_counter()
    print("{0} ms".format((end-start)*1000.0))
pythonオブジェクトの送信から受信までの時間をms単位で出力するようにしています。

実行結果


実行は、端末を2つ立ち上げて、上記のコードをpythonで実行するだけです。

実行してみると、Subscriber側では、
115.46662700129673 ms
0.5929949984420091 ms
0.9222489970852621 ms
0.7364909979514778 ms
0.5779260027338751 ms
0.5179920044611208 ms
0.5140290013514459 ms
0.5147350020706654 ms
0.5380119982874021 ms
0.5411919992184266 ms
0.5420840025180951 ms
0.5289019973133691 ms
0.8248919984907843 ms
0.9655120011302643 ms
0.7765349946566857 ms
0.6397230026777834 ms
0.7840489997761324 ms
1.0322410016669892 ms
0.7769410003675148 ms
0.774603002355434 ms
0.7632400011061691 ms
0.7989099976839498 ms
0.8185699989553541 ms
0.8329580014105886 ms
と出力されました。おおむね1ms以下で送信できているようです。ただ通信したいだけの用途なら十分な速さですね。

送信データ量を増やす


numpy arrayを作るところを1000倍してみたところ、
w = np.ones(160000)
さすがに3ms前後かかるようになりました。
118.689604998508 ms
6.79611799569102 ms
3.675059000670444 ms
3.75746200006688 ms
3.62611300079152 ms
3.6894719960400835 ms
2.672021997568663 ms
3.772675001528114 ms
3.641210001660511 ms
3.5925789998145774 ms
3.0622690028394572 ms

通信をtcpに変更


さらに、通信を次のようにtcpに変更してみました。
[Publisher]
socket.bind("tcp://*:5556")

[Subscriber]
socket.connect("tcp://localhost:5556")
すると、
117.72498200298287 ms
6.720707002386916 ms
4.039794999698643 ms
3.9917790054460056 ms
4.0376230026595294 ms
3.933735999453347 ms
3.9876959999674 ms
4.071578005095944 ms
4.004092996183317 ms
となりました。ipcよりtcpのほうが若干遅いようです。

2018/11/11

場所によって異なる興奮性ニューロン

ニューロンは色々種類があるけれど、満遍なく存在するわけではないらしい。 というわけで、以下ざっくりとメモ。

ざっくり


Tasicらによると、マウスの脳の異なる領域にある興奮性ニューロンはタイプが異なるらしい[1, 2]。

Tasicらは、マウスの脳のAnterior lateral motor cortex (ALM, 運動野のどこか)とPrimary visual (VISp, 視覚野)を対象として、遺伝子発現プロファイルに基づいて細胞タイプを調べた。 そうしたところ、133個の細胞タイプが存在し、101個のタイプがALMに、111個のタイプがVISpにあることが分かった。そのうち、79個は両方の領域で存在することもわかった。

また、おおきく3つに分類すると、抑制(GABAergic)タイプが61個、興奮(glutamatergic)タイプが56個、ニューロンでないタイプが16個、となった。

これらのうち、興奮タイプのニューロンの大部分が上記2つの領域間で共有されていなかった。 抑制タイプの中には領域間で遺伝子発現が異なるものがあったが、統計的に異なるというには不十分であった([2]のFig.2 cを参照)。

ちょこっと計算


計算してみると、領域間で共有されていないタイプはALMでは22個、VISpでは32個あり、合計で54個となります。 共有の有無の基準は良く読まないとわかりませんが、この54個がすべて興奮タイプのニューロンだということのようです。[2]のFig.2 cを見ると、確かに興奮性のものばかり遺伝子発現に差異があるようです(図のDE genesはdifferentially expresed genesの略かな?)。

参考


[1] https://www.nature.com/articles/d41586-018-07027-3
[2] https://doi.org/10.1038/s41586-018-0654-5

Firefoxのタブの設定

Firefoxで新しいタブを開く条件を設定する方法です。
動作確認は63.0.1で実施しています。

まず、about:configを開きます。

ポップアップウィンドウをタブで開くには、

browser.link.open_newwindow.restriction 0
とします。

ロケーションバーから開くときに新しいタブで開くには、

browser.urlbar.openintab true
とします。

ブックマークから開くときに新しいタブで開くには、

browser.tabs.loadBookmarksInTabs true
とします。

検索ボックスから開くときに新しいタブで開くには、

browser.search.openintab true
とします。

これで、何か新しいURLを開こうとしたときに、常に新しいタブで開くようになります。

2018/10/06

LVMでパーティション作成

はじめに


LVMでディスクを設定します。細かい説明は適当にぐぐってください。

環境


VirtualBox環境で、2GBのHDDを2個目のHDDとして追加しています(/dev/sdb)。 この追加したHDDをLVMにします。OSはUbuntu 16.04.4 LTSです。

まとめ


$ sudo parted /dev/sdb
$ (parted) mklabel gpt
$ (parted) mkpart base-partition 0% 100%
$ (parted) set 1 lvm on
$ (parted) quit
$ sudo pvcreate /dev/sdb1
$ sudo vgcreate vg0 /dev/sdb1
$ sudo lvcreate -l 100%FREE -n lv0 vg0
$ sudo mkfs.ext4 /dev/vg0/lv0
$ sudo mkdir /disk1
$ sudo mount /dev/vg0/lv0 /disk1

詳細


  1. まずは元にするパーティションが必要なので、partedで作成します。
    $ sudo parted --list 
    
    で一覧を取得して、対象となるデバイスを選びます。 不明な点があればman partedを読みましょう。 parted --helpは内容を知っている人向けです。
  2. 選択したデバイスを指定してpartedを起動します。
    $ sudo parted /dev/sdb
    
  3. パーティションの情報を表示します。
    (parted) print
    ...
    パーティションテーブル: unknown
    ...
  4. パーティションの管理方式をGPTに変更します。
    (parted) mklabel gpt
    (parted) print
    ...
    パーティションテーブル: gpt
    ...
  5. パーティションを作成します。
    (parted) mkpart base-partition 0% 100%
    (parted) print
    モデル: ATA VBOX HARDDISK (scsi)
    ディスク /dev/sdb: 2147MB
    セクタサイズ (論理/物理): 512B/512B
    パーティションテーブル: gpt
    ディスクフラグ: 
    
    番号  開始    終了    サイズ  ファイルシステム  名前            フラグ
     1    1049kB  2146MB  2145MB                    base-partition
    
    man partedで表示されるマニュアルでは、
    mkpart part-type [fs-type] start end
                         Make  a  part-type  partition  for filesystem fs-type (if
                         specified), beginning at start  and  ending  at  end  (by
                         default  in megabytes).  part-type should be one of "pri‐
                         mary", "logical", or "extended".
    
    と書かれていますが、第1引数に指定した文字列は、パーティションの名前になります。
  6. LVM用のフラグを立てます。
    (parted) set 1 lvm on
    (parted) p                                                                
    モデル: ATA VBOX HARDDISK (scsi)
    ディスク /dev/sdb: 2147MB
    セクタサイズ (論理/物理): 512B/512B
    パーティションテーブル: gpt
    ディスクフラグ: 
    
    番号  開始    終了    サイズ  ファイルシステム  名前            フラグ
     1    1049kB  2146MB  2145MB                    base-partition  lvm
    

    parted上での作業は完了ですので、quitで抜けます。

  7. Physical Volumeを作ります。
    $ sudo pvcreate /dev/sdb1
    sdbではなく、sdb1を指定します。 結果は
    $ sudo pvdisplay
    で確認できます。
  8. Volume Groupを作ります。
    $ sudo vgcreate vg0 /dev/sdb1
    結果は
    $ sudo vgdisplay
    で確認できます。
  9. Logical Volumeを作ります。サイズ指定なら
    sudo lvcreate -L 1.996GiB -n lv0 vg0
    全て使うなら
    sudo lvcreate -l 100%FREE -n lv0 vg0
    とします。 出来上がったサイズは
    $ sudo lvdisplay
      --- Logical volume ---
      LV Path                /dev/vg0/lv0
      LV Name                lv0
      VG Name                vg0
      ...
      LV Size                2.00 GiB
      ...
    で確認できます。
  10. ext4でフォーマットします。
    $ sudo mkfs.ext4 /dev/vg0/lv0
  11. 出来上がったディスクをマウントします。
    $ sudo mkdir /disk1
    $ sudo mount /dev/vg0/lv0 /disk1
    $ df
    Filesystem                  1K-blocks     Used Available Use% Mounted on
    /dev/mapper/vg0-lv0           2027408     3072   1903300   1% /disk1
    
    マウントするときは、UUIDでも可能です。
    $ blkid
    /dev/mapper/vg0-lv0: UUID="0805787f-b7a0-488a-9321-ed7ed77322ab" TYPE="ext4"
    
    $ sudo mount UUID=3be08a7c-449a-4d74-b576-1064ef72a7ef /disk1
    

    fstabに追加したいときは、

    $ man fstab
    を参照するかぐぐりましょう。