物理のバス停 by salt22g

とある物理学見習いの備忘録。

二つのファイルに保存されている画像を合成する

python version

import numpy as np
import cv2
import os
import glob
import sys
import re

args = sys.argv
filename1 = args[1]
filename2 = args[2]
outname = args[3]


if __name__ == "__main__":


    new_dir = outname + "/"
    os.makedirs(new_dir, exist_ok=True)

    files1 = glob.glob(filename1 + "/*.png")
    files1.sort()

    files2 = glob.glob(filename2 + "/*.png")
    files2.sort()

    i = 0
    for file1, file2 in zip (files1, files2):
        img1 = cv2.imread(file1)
        img2 = cv2.imread(file2)
        merge_img = cv2.add(img1, img2)
        cv2.imwrite(new_dir + "{:08}.png".format(i), merge_img)
        i += 1

C++ vesion

前半部分はこの方のコードを拝借。
Python built-in zip() method implemented in C++ · GitHub

#include "opencv2/opencv.hpp"

#include "TStyle.h"
#include "TFile.h"
#include "TTree.h"

#include "TMath.h"
#include "TRandom.h"

#include <stdio.h>
#include <stdlib.h>
#include <string>

#include <iostream>
#include <fstream>
#include <sstream>
#include <math.h>
#include <random>
#include <vector>

#include <sys/stat.h>
#include <glob.h>


template<typename T>
class Zip
{
    
public:
    
  typedef std::vector<T> container_t;
    
  template<typename... Args>
  Zip(const T& head, const Args&... args)
    : items_(head.size()),
      min_(head.size())
  {
    zip_(head, args...);
  }
    
  inline operator container_t() const
  {
    return items_;
  }
    
  inline container_t operator()() const
  {
    return items_;
  }
    
private:
    
  template<typename... Args>
  void zip_(const T& head, const Args&... tail)
  {
    // If the current item's size is less than
    // the one stored in min_, reset the min_
    // variable to the item's size
    if (head.size() < min_) min_ = head.size();
        
    for (std::size_t i = 0; i < min_; ++i)
      {
	// Use begin iterator and advance it instead
	// of using the subscript operator adds support
	// for lists. std::advance has constant complexity
	// for STL containers whose iterators are
	// RandomAccessIterators (e.g. vector or deque)
	typename T::const_iterator itr = head.begin();
            
	std::advance(itr, i);
            
	items_[i].push_back(*itr);
      }
        
    // Recursive call to zip_(T, Args...)
    // while the expansion of tail... is not empty
    // else calls the overload with no parameters
    return zip_(tail...);
  }

  inline void zip_()
  {
    // If min_ has shrunk since the
    // constructor call
    items_.resize(min_);
  }

  /*! Holds the items for iterating. */
  container_t items_;
    
  /*! The minimum number of values held by all items */
  std::size_t min_;
    
};

template<typename T, typename... Args>
typename Zip<T>::container_t zip(const T& head, const Args&... tail)
{
  return Zip<T>(head, tail...);
}


std::vector<std::string> get_file_path(std::string input_dir, std::string wildcard){
  glob_t globbuf;
  std::vector<std::string> files;
  glob((input_dir + wildcard).c_str(), 0, NULL, &globbuf);
  for (int i = 0; i < globbuf.gl_pathc; i++){
    files.push_back(globbuf.gl_pathv[i]);
  }
  globfree(&globbuf);
  return files;
}


int main(int argc, char *argv[]){

  std::string input_dir1 = argv[1];
  std::string input_dir2 = argv[2];
  std::string output_dir = argv[3];
  
  std::string png = "/*.png";

  mkdir(Form("result_image/%s", output_dir.c_str()), S_IRWXU | S_IRWXG | S_IRWXO);

  std::vector<std::string> events1 = get_file_path(input_dir1, png);
  std::vector<std::string> events2 = get_file_path(input_dir2, png);
  std::sort(events1.begin(), events1.end());
  std::sort(events2.begin(), events2.end());

  std::string f;
  int i = 0;

  for (auto j : zip(events1, events2)){
    
    std::cout << j[0] << std::endl;
    std::cout << j[1] << std::endl;
    cv::Mat img1 = cv::imread(j[0], cv::IMREAD_COLOR);
    cv::Mat img2 = cv::imread(j[1], cv::IMREAD_COLOR);
    
    cv::Mat out_img;
    cv::add(img1, img2, out_img);
    
    cv::imwrite(Form("result_image/%s/%08d.png",output_dir.c_str(), i), out_img);
    i += 1;
  }
}

素粒子原子核物理解析用 Docker image

Docker imageを作成しました。Dockerhubでも公開しています。
f:id:salt22g:20200513060910p:plain
https://hub.docker.com/repository/docker/salt22g/docker-rpg

こちらの記事でも書きましたが、環境構築というのはとても面倒で、パッケージのversionが変わると不具合があったり、何度繰り返しても毎回違うところでつまずくことがあったりします。そこで原子核物理理系大学院生である私が普段使っている解析ツールをDocker imageにしてまとめてみました。その名もdocker-rpg(名前がダサい…??)

想定している使い方

  • 中央計算機、サーバーなどでの使用(Linux)
  • ローカルでの開発環境テスト
  • 開発環境の共有(後輩へのプログラミング講習会etc)

docker-rpgはどんなDocker imageなのか

R - ROOT
[https://root.cern.ch/}
物理解析の基本的な解析ツールで多くの素粒子原子核の研究室で使用されており、大量のデータを解析する際に重宝されます。グラフの描画ツールとしても優秀で、解析と同時に物理学っぽい(怒られそう)図を作ることができます。

f:id:salt22g:20200513062437p:plain
rootの起動画面
f:id:salt22g:20200513062809p:plain
rootのdemoプログラムより

P- python3
https://www.python.jp/
現在最も開発が盛んでプログラミングのスタンダードとなっている言語です。初心者にも理解しやすく、画像処理からデータ解析、機械学習まで用途は様々。
筆者の主言語もpython3です。

G- Geant4
https://geant4.web.cern.ch
モンテカルロシミュレーションによって粒子の物理過程をシミュレーションできるツールです。
物理実験はもちろん医療分野においてもよく用いられています。

f:id:salt22g:20200513062124p:plain
https://indico.physics.lbl.gov/event/581/contributions/1401/attachments/1284/1424/Geant4_CesarGR.pdf

これらの開発ツールは導入が面倒です。
macOSではhomebrewがあるので簡単になっていますが、他の人と共有しているLinuxサーバーでは制約の大きさから依存関係のパッケージをインストールできず導入ができないことがあります。

また、ROOTとGeant4についてはWindows PC上では上級者でないと動かすのはなかなか大変です。
基本的にLinuxの仮想環境またはWSLを立ち上げる必要があります。
phst.hateblo.jp
(↑こちらの記事は参考になります。)

そこで、是非このDocker imageで開発環境をサボりましょう。

使用時の様子

Mac

f:id:salt22g:20200513054238p:plain

Linux (Ubuntu)

f:id:salt22g:20200513070547p:plain
Linuxサーバ上でbuildしたGeant4 マクロの実行中の様子

Windows

まだテストできてない。
近々追記する予定です。

pythonのglobで末端のファイル名だけ取得したい

pythonのglob.globによるファイル名の一括取得。便利ですよね。
glob君に任せ切りにしていたら一瞬迷ったのでメモとして残します。
全然大した話ではないのですし、もっと良いやり方があるかもしれません。

2020/05/17 15:20 追記

os.path.basename()

これで行けるらしい。俺がアホだった。

cv2.imwrite(new_dir + os.path.basename(file1), merge_img)

問題

こんな処理をしたい時がありました。

  • 二つのファイルに同じ名前だが異なる処理をした画像がある。
  • これらのファイルから同じ名前の画像を読み込み横につなげる。
  • 別のファイルに元と同じ名前で書き出す。

ファイル構造はこんな感じ。名前は連番ではない場合もあります。

current_dir/
  ┠ gattai.py
  ┠ images_A/
  ┃  ┠ abcdefg.png
  ┃  ┠ hijk.png
  ┃  ┠ lmnopq.png
  ┃  ┠ 以下略
  ┠ images_B/
  ┃  ┠ abcdefg.png
  ┃  ┠ hijk.png
  ┃  ┠ lmnopq.png
  ┃  ┠ 以下略

↑を↓にしたい。

current_dir/
  ┠ gattai.py
  ┠ images_A/ #略
  ┠ images_B/ #略
  ┠ images_C/ #自分で名前をつけて作成
  ┃  ┠ abcdefg.png #処理されて合体した画像
  ┃  ┠ hijk.png
  ┃  ┠ lmnopq.png
  ┃  ┠ 以下略

最初は何も考えずこんなコードを書きました。

import numpy as np
import cv2
import os
import glob
import sys
import re

args = sys.argv
filename1 = args[1] #images_A
filename2 = args[2]  #images_B
outname = args[3]   #images_C

if __name__ == "__main__":

    new_dir =  outname + "/"
    os.makedirs(new_dir, exist_ok=True) #出力用のファイルを生成

    files1 = glob.glob(filename1 + "/*.png") #ファイル内の画像のパスを取得
    files1.sort()
    files2 = glob.glob(filename2 + "/*.png")
    files2.sort()


    for file1, file2 in zip(files1, files2): #読み込む画像パスを名前をループで取得
        img1 = cv2.imread(file1)
        img2 = cv2.imread(file2)
        images = [img1, img2]
        merge_img = np.concatenate(images, axis=1)
        cv2.imwrite(new_dir + file1, merge_img)

実行してみると…

python3 gattai.py images_A images_B images_C

実はこの部分がまずい。

new_dir + file1 ## => images_C/images_A/hogehoge.png

globで取得したfile1には
images_A/hogehoge.png
というように相対パスが全て書き込まれます。

os.makedirs(new_dir, exist_ok=True)

で作成したのはimages_Cまでなのでその中のimages_Aは作成されておらず画像は保存できません。

f:id:salt22g:20200517053640p:plain
模式図(わかりづらい)

解決

pythonの文字列分割機能を使って解決しました。
qiita.com

phrase = "If you can dream it you can do it"
# 「split」使用 .split(区切り文字, 分割数)
print(phrase.split()) ## => ["If","you","can","dream","it","you","can","do","it"]

word = "HelloWorld"
# 「rsplit」使用 .rsplit(区切り文字, 分割数)
print(word.rsplit("l",1)) ## => ['HelloWor', 'd']

今回globで取得したパスは
/images_A/hogehoge.png
取得したいのはhogehoge.pngの部分なので、"/"で区切った末端の部分を取れば良いですね。

phrase.split("/") ## => ["", "images", "hogehoge.png"]
phrase.split("/")[-1] ## => ["hogehoge.png"]

ということで最後のcv2.imwriteをこのように書き換えました。

cv2.imwrite(new_dir + file1.split("/")[-1], merge_img)

これで当初の狙い通りの出力になりました。めでたし。

Docker locale設定のerrror

Docker image作成時に起きたlocaleに関するerrorを解決しました。
ググっても「Ubuntuの日本語化」に記事ばかりで遠回りになってしまい、しばらくうまく行きませんでした。
最終的に解決法がわかったのでこの記事に書いておきます。

環境

hostOS: macOS & ubuntu(どちらでも同じ症状)
Docker version: 19.03.8
元にしたDocker image: ubuntu:18.04(公式)
hub.docker.com

症状

# docker run -it hogehoge
manpath: can't set the locale; make sure $LC_* and $LANG are correct
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)

使っている分には影響はなかったがなんか気持ち悪い。
調べてみるとlocale(言語設定)の問題らしい。

# locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=en_US.UTF-8

正しく設定できていないとのこと。
使えるlocaleを確認すると…

# locale -a
C
C.UTF-8
POSIX

え…デフォルトに英語ないの??!…公式imageなのに…

解決法1

Dockerfikeに以下の記述を追加

# locale
RUN apt-get update
RUN apt-get install -y locales
RUN locale-gen en_US.UTF-8

これでen_US.UTF-8の環境が構築されるのでerrorが消える。

解決法2

コンテナ内で以下のコマンドを叩く

# apt-get update
# apt-get install -y locales
# locale-gen en_US.UTF-8

これで起動時のerrorも消えてスッキリしました。
↓解決後

# locale
LANG=ja_JP.UTF-8
LANGUAGE=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=en_US.UTF-8

# locale -a
C
C.UTF-8
en_US.utf8
POSIX

ちゃんと追加されてますね。

大学院生Macユーザーへ Dockerの勧め

最近研究でDockerを使うようになり、Linuxサーバー上で実行環境を作成するなどしていました。
使っているうちに「Docker面白いな」と感じたので手持ちのMac上でも使えるようにして詳しくなりたいと思った次第です。

f:id:salt22g:20200511183927p:plain
Dockerのクジラ可愛い

Dockerとはなんぞ。

qiita.com
色々なサイトで紹介されています。上記のサイトは余計な絵が少なくておすすめ。
筆者はDockerを環境構築をサボれるツールだと考えています。
「全く同じ開発環境(Docker image)をどのマシンでも使うことができる。」
これはチームでの開発はもちろんですが、理系大学院生にとって後輩への引継ぎ、指導が楽になることを意味します。

研究室で必要なツールを1から後輩のPCにインストールするとなると指導や質問対応に多くの時間を割かねばなりません。
それはそれぞれのPCの環境(OSやバージョン)が異なることが大きな理由の一つです。
特にMacユーザーの私がプログラミングの講習会をする際には、Windowsユーザーのことも考える必要がありました。
手元の動かせるWindowsマシンで環境構築を試さなければなりません。
Dockerを導入すれば同一の開発環境をWindowsでもmacOSでも準備できます。
今回はまず、MacOSへの導入についてです。

Dockerの導入

まずはmacOSにインストール。
環境はこちらの記事を参照。今回もHomebrewを使います。
salt22g.hatenablog.jp

CUI及びGUIアプリを取得。

% brew install docker
% brew cask install docker

インストールできたか確認。

% docker --version
Docker version 19.03.8, build afacb8b

f:id:salt22g:20200507095505p:plain
メニューバーにコンテナを載せたクジラが出現

Dockerの主なコマンドはこちらのサイトによくまとまっています。
qiita.com

Docker imageを作成、起動

今回作ったDocker imageについては後日詳しくお話しします。
Dockerfileの作り方についてもこのブログで公開する予定です。

Dockerfileを作成したフォルダに移動して

% docker build -t <イメージ名> .

イメージが作成できているか確認してみましょう。

% docker images
REPOSITORY                              TAG                 IMAGE ID                  CREATED                 SIZE
自分が作ったイメージ名            latest              イメージID               作成された時刻        ファイルサイズ
ubuntu                                         18.04               c3c304cb4f22        2 weeks ago             64.2MB

自分が作ったイメージの名前が表示されればOKです。
早速起動しましょう。

% docker run -it <イメージ名>
[root@8fc1f79b0233] /workspace

docker runには様々なoptionがあるのでそれはまたどこかで…

自分のイメージをDockerhubにpush

Docker hub
作成したimageをクラウド上で管理するのがDocker hubです。
他の人が作ったimageはもちろん。公式imageも掲載されています。
今回は筆者が作ったimageをpushしてみます。

アカウントを作成
hub.docker.com

f:id:salt22g:20200507095954p:plain
アカウントが作れたらクジラをクリックしてsign inしておく。

予めdockerhubでリポジトリを作成。

f:id:salt22g:20200507104735p:plain
リポジトリを作成

f:id:salt22g:20200508153426p:plain
こんな感じ

% docker tag <ビルドしたimageの名前> <DockerhubのユーザID>/<さっきDockerhubで作ったリポジトリ名>:<tag名(latestとするのが一般的)>
% docker login -p="Dockerhubのパスワード" -u="Dockerhubのユーザー名"
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded

なんかWARNINGが出たけれど無視。
Login Succeededが出ればOK。

docker push salt22g/docker-rpg:latest

f:id:salt22g:20200511012440p:plain
push後のリポジトリ画面

GUIを使ってみる

まずはsocatをインストール。

brew install socat

XQuartzもインストール。
salt22g.hatenablog.jp
この記事でも紹介したソフトです。
sshで接続した先の画像やGUIを取得するサーバーみたいなものです(多分)

brew install caskroom/cask/brew-cask
brew cask install xquartz
open -a XQuartz #普通にlaunchpadから起動してもOK

f:id:salt22g:20200513051908p:plain
XQuartzを起動するとxtermという画面が出てきます。

Docker側から見たMac(hostマシーン)のIPアドレスを取得。

ifconfig en0 | grep inet | awk '$1=="inet" {print $2}'
    xxx.xxx.xxx.xxx #ipアドレス

socatを起動。

socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\"

ターミナルから返答がないはずなので別ターミナルからdockerを以下のオプション付きで起動。

docker run -it <イメージ名>-e DISPLAY="xxx.xxx.xxx.xxx:0"

f:id:salt22g:20200513053506p:plain
こんな感じ

dockerコンテナ内で作成したグラフが表示できるか見てみましょう。

f:id:salt22g:20200513054132p:plain
pythonのmatpltlibとか
f:id:salt22g:20200513054238p:plain
ROOTとか

後輩や部下の環境構築に困っている方々いかがでしょう。

Docker内でのopencv buildでerror…

とあるDocker imageをbuildしようとしていたところ…
opencvのbuild中にerrorが

Dockerfileはこんな感じ…

FROM ubuntu:18.04

--中略--

# install & build opencv
RUN git clone https://github.com/opencv/opencv.git ~/opencv
RUN git clone https://github.com/opencv/opencv_contrib.git ~/opencv_contrib
RUN cd ~/opencv && \
    mkdir build && \
    cd build && \
    cmake -D CMAKE_BUILD_TYPE=RELEASE \
        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D INSTALL_PYTHON_EXAMPLES=ON \
        -D INSTALL_C_EXAMPLES=OFF \
        -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules ~/opencv \
        -D BUILD_EXAMPLES=ON .. && \
    make -j4 && \
    make install && \
    /sbin/ldconfig

--以下略--

makeの部分でerrorが出た模様。

c++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-7/README.Bugs> for instructions.
make[2]: *** [modules/python2/CMakeFiles/opencv_python2.dir/__/src2/cv2.cpp.o] Error 4
make[1]: *** [modules/python2/CMakeFiles/opencv_python2.dir/all] Error 2
CMakeFiles/Makefile2:19251: recipe for target 'modules/python2/CMakeFiles/opencv_python2.dir/all' failed
make[1]: *** Waiting for unfinished jobs....

error文で検索すると。
github.com

ちゃんと読んでないけどRAMが何たらかんたらと書いてある。
Linuxのサーバ上では同じDockerfileでbuildできていたのでおそらくPCの性能的な問題。

Macのスペック
OS: macOS Catalina 10.15.4
機種名:     iMac(21.5-inch, 2019)
プロセッサ:    3.2 GHz 6Core Intel Core i7
メモリ:      16 GB 2667 MHz DDR4
グラフィックス:  Radeon Pro 555X 2 GB


コア数は足りているはずなのにうまく行ってない。

ということで

make -j4 → make -j2

ちゃんとmakeできました。
まだしっかり調べていないですが、docker内でのcpu使用に制限があるのか…
またわかったら記事にします。

Geant4 cmakeでerror…(macOS)

Geant4のフレームワークをcmakeしようとしたところここでエラーが…

Failed to find "gl.h" in

CMake Error at /usr/local/opt/qt/lib/cmake/Qt5Gui/Qt5GuiConfigExtras.cmake:9 (message):
Failed to find "gl.h" in
"/System/Library/Frameworks/OpenGL.framework/Headers;/System/Library/Frameworks/AGL.framework/Headers".
Call Stack (most recent call first):
/usr/local/opt/qt/lib/cmake/Qt5Gui/Qt5GuiConfig.cmake:202 (include)
/usr/local/Cellar/geant4/10.5.1/lib/Geant4-10.5.1/Geant4Config.cmake:449 (find_package)
CMakeLists.txt:13 (find_package)

 

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/OpenGL.framework/Headers
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AGL.framework/Headers

なんか場所が変わっていたらしい。

ちなみにGeant4はhomebrewを使ってインストールしたもの。

/usr/local/opt/qt/lib/cmake/Qt5Gui/Qt5GuiConfigExtras.cmake

このファイルを見に行くと

set(_GL_INCDIRS "/System/Library/Frameworks/OpenGL.framework/Headers" "/System/\
Library/Frameworks/AGL.framework/Headers")

ということでここを編集してみる

一応元のファイルは残して…

set(_GL_INCDIRS "/System/Library/Frameworks/OpenGL.framework/Headers" "/System/Library/Frameworks/AGL.framework/Headers" "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AGL.framework/Headers" "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/OpenGL.framework/Headers")

このように編集

再度

cmake ..

-- Configuring done
-- Generating done
-- Build files have been written to: /Users/hogehoge/

やったぜ。