メインコンテンツに移動

可視化

多次元のデータを計算するようになると,1次元の可視化ソフトでは役に立ちません.多次元データが取り扱える可視化ソフトを使ってみましょう.

ParaView

ParaViewは, KITWare社の製品ですが,無料で利用できます.科学技術各方面で利用されていますが, 詳細はここにガッツリ書いてあるので,ここではさわりだけね.

インストールはこちらから

ファイル形式

ParaViewはさまざまなデータ形式を読み込むことができます.が,まあ正式はVTKデータというものです.

の説明がリンク先に書いてあります.まず単純にLEGACY-VTKを書いてみましょう.

LEGACY-VTKを出力

簡単な例として, \[f(x,y)=\exp\left[-(x^2+y^2)\right],\quad -3<x<3,\ -3<y<3\]を出力してみましょう.VTKには格子に応じていろいろな形式があります.一例は

image data rectlinear data structured grid
等分割直交格子 不等分割直交格子 曲線座標格子 

まずはimage dataですよね!

VTK-ImageData

 

右図の解説に間違いはない. クラスになってないが,まあ,最初のサンプルとしては十分じゃろ.目的に合わせて書き換えてみた

 書き換えてみた

パラメータにずらずら書くとバグの素なのでクラスに入れといた. 

//  vtk_myIO.hpp    僕のVTKの入出力
#pragma once
#include <fstream>
#include <string>
#include <Eigen/Dense>
class vtk_myIO
{
public:
   std::filesystem::path filename;
   Eigen::Vector3d ox;
   Eigen::Vector3d dx;
   Eigen::Vector3i nx;
   void write_legacy_vti2d(std::function<double(int,int)> data)
   {
       std::filesystem::create_directories(filename.parent_path());
       std::ofstream ofs(filename);
       ofs << "# vtk DataFile Version 3.0\n";
       ofs << "Legacy VTK file generated by write_legacy_vtk2d\n";
       ofs << "ASCII\n";
       ofs << "DATASET STRUCTURED_POINTS\n";
       ofs << "DIMENSIONS " << nx[0] << " " << nx[1] << " 1\n";
       ofs << "ORIGIN " << ox[0] << " " << ox[1] << " 0.0\n";
       ofs << "SPACING " << dx[0] << " " << dx[1] << " 1.0\n";
       ofs << "POINT_DATA " << nx[0] * nx[1] << "\n";
       ofs << "SCALARS scalars double\n";
       ofs << "LOOKUP_TABLE default\n";
       ofs << std::setprecision(15) << std::scientific;
       for (int j = 0; j < nx[1]; ++j)
           for (int i = 0; i < nx[0]; ++i)
               ofs << data(i,j) << "\n";
       ofs.close();
   }
};

いろいろ試す時に面倒なので,潰しが効くようにしといたここは,呼び出す時に,なんかi, jから値が出るのを与えれば良いことにしとくこれは入れといた方が安全だということを前に学んだよね.ここは,標準だと7-8桁しか保存せず結局不足することが多いので,全桁書くように設定した.するってえと, main()は

// main.cpp
#include <iostream>
#include "../include/vtk_myIO.hpp"
int main()
{
   vtk_myIO Writer;
   Writer.ox << -3,-3, 0;      //めんどくさがってvtk_write.oxをEigen::Vector3d にしたので,3成分入れてね
   Writer.dx << 0.1, 0.1, 0.;
   Writer.nx << 60, 60, 1;
   std::filesystem::path myHome = getenv("HOME");
   Writer.filename=(myHome/"Data")/"test05";       // あなたが好きなデータフォルダーを設定
   Writer.filename/="LEGACY_2D.vtk";         // ファイル名は  ほげほげ.vtk にしてね!
   Writer.write_legacy_vti2d([&Writer](int i, int j) -> double
   {
       double x=Writer.ox[0]+Writer.dx[0]*i;
       double y=Writer.ox[1]+Writer.dx[1]*j;
       return std::exp(-x*x-y*y);
   });
   return 0;
}

これで LEGACY_2D.vtk ファイルが完成した!

VTKファイルが完成したら, ParaViewを起動してみましょう.

 

結構独特な操作系なので,いろいろクリックしてみてくださいね!

ParaViewの操作方法についても, AIモンキーは懇切丁寧に答えてくれますよ.

XML-VTKの作成

従来, XML-VTKを出力するプログラムを作成するのは困難でした.だが,AIモンキーが書いてくれるので,今やなんの困難もありません.XML-VTKを用いると

  • データファイルが,小さい.
  • LEGACY形式では,書き出したデータを読み込むと,数値が異なる.文字列で有限桁しか出力しないからである.XML-VTKでは,そんなことはない
  • LEGACY形式では,セルにデータを割り当てたり,格子にデータを割り当てたり,境界面にデータを割り当てるのが,とても面倒である.
  • LEGACY形式では,密度(スカラー量)と流速(ベクトル量)を入れたりするのが,まあ面倒ってか,結構細かい性格の人じゃないと無理ゲーである(一行違っただけで,大騒動である.それくらい,ええやん?と作者が思っても,動作してくれない). XML-VTKは,アバウトな感じの性格でも大丈夫である
VTKライブラリーのインストール

ここからダウンロードしてインストールします.

  • HomeBrew, MacPort人は,上のファイルをインストールしてはいけません.自分でなんとかしてくださいね.
  • ファイルサイズが0になってますが,本当は 214226063バイト(200MBくらい)あるので,気をつけてね

さて,インストールしてもVSCodeのInteliSenseがVTKファイル(/usr/local/include/vtk-9.4)を認識しないので,編集中にうだうだ赤線をつけてしまい,うざい.

そこはAIモンキーに, InteliSenseのC/C++ includepathを訂正したいとか要求して編集しておこう:

VTK-ImageData

まずは簡単なやつですね!

 クソ面倒なVTKマニュアルをサルが読んで,教えてくれるのだ

でも さっきのクラスに追加する方がええな

追加してみた. ん?というところもあったので,少し猿と対話したので,上のchat結果とはちょっと違うかも

// vtk_myIO.hpp    僕のVTKの入出力
...
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>
#include <vtkXMLImageDataWriter.h>
...
    void write_xml_vti2d(std::function<double(int, int)> data)
   {
       // VTKのImageDataオブジェクト作成
       auto image = vtkSmartPointer<vtkImageData>::New();
       image->SetDimensions(nx[0], nx[1], nx[2]);
       image->SetOrigin(ox[0], ox[1], ox[2]);
       image->SetSpacing(dx[0], dx[1], dx[2]);
       // スカラー値の配列を作成
       auto ARR= vtkSmartPointer<vtkDoubleArray>::New();
       ARR->SetName("pressure");
       ARR->SetNumberOfComponents(1);
       ARR->SetNumberOfTuples(nx[0] * nx[1]);
       image->GetPointData()->AddArray(ARR);
       for (int j = 0; j < nx[1]; ++j)
           for (int i = 0; i < nx[0]; ++i)
               ARR->SetValue(j * nx[0] + i, data(i, j));
       // スカラー値の配列をアクティブにする
       image->GetPointData()->SetActiveScalars("pressure");
       // XML VTKファイル(.vti)で出力
       auto writer = vtkSmartPointer<vtkXMLImageDataWriter>::New();
       std::filesystem::create_directories(filename.parent_path());
       writer->SetFileName(filename.c_str());
       writer->SetInputData(image);
       writer->SetCompressorTypeToLZMA(); // LZMA圧縮(zlibより高圧縮)
       writer->Write();
   }
...

ここでは,まず①配列を作成する, ②配列に名を与える, ③配列の要素数を指定する, ④その配列を格子点データに追加する⑤配列の値を設定する(ここで学んだように1次元化されていることに注意) 少し簡単な方法もある(スカラー1個だけは簡単)が,まあ流体計算では,セルデータに圧力だけじゃなくて流速ベクトルとか設定しないとだしね.ここの方法だと,複数のデータを一つのVTKに追加できるし,追加した関数は全てParaViewで一覧に出現する.これは省略しても大して変わらない.

この例では格子点データに追加しましたが,もちろん,セルデータに追加することもできますが・・・少し面倒なので,ここではやめときましょう.

main()はこんな感じ:

#include <iostream>
#include "../include/vtk_myIO.hpp"
int main()
{
   vtk_myIO Writer;
   Writer.ox << -3, -3, 0;
   Writer.dx << 0.01, 0.01, 0.;
   Writer.nx << 600, 600, 1;
   std::filesystem::path myHome = getenv("HOME");
   auto myFolder = (myHome / "Data") / "test05";
   auto FUNC = [&Writer](int i, int j) -> double
   {
       double x = Writer.ox[0] + Writer.dx[0] * i;
       double y = Writer.ox[1] + Writer.dx[1] * j;
       return std::exp(-x * x - y * y);
   };
   Writer.filename = myFolder / "LEGACY_2D.vtk";
   Writer.write_legacy_vti2d(FUNC);
   Writer.filename = myFolder / "XML_2D.vti";
   Writer.write_xml_vti2d(FUNC);
   return 0;
}

LegacyタイプとXMLタイプの2通り使うので,使いまわせるようにFUNC()というのにしておきました.

こっちは,先ほどと同じLegacyVTK(ファイル名が.vtk)

こっちが,XML-VTK(ファイル名が.vti)    ⭐︎ファイル名を正しく設定しておくと, ParaViewで自動で判断してくれるので便利

外部ライブラリーを使うので,ビルドするには再生ボタンよりもCMakeの方が良いです.AIモンキーに聞いて作成しましょう:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
set(CMAKE_OSX_SYSROOT macosx)
project(MultiTargets)
# C++20を有効化
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build-Modeの設定
# set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_BUILD_TYPE Release)
# Library Search
cmake_policy(SET CMP0167 NEW)
find_package(Boost REQUIRED COMPONENTS timer)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
# Targets
...
# myIOターゲット
add_executable(myIO myIO/main.cpp)
target_include_directories(myIO PRIVATE include)
target_link_libraries(myIO PRIVATE ${VTK_LIBRARIES})

この辺が,新しい感じですね!(だが,時にfind_package()の挙動がバージョンアップで変わったりするので,そこは注意)

で, buildして実行したら

% cmake ..
% make myIO
% ./myIO
% ls -l $HOME/Data/test05 あなたのデータフォルダーは違う場所にあるでしょうね
total 22664
-rw-r--r--@ 1 sugimoto  staff  7920229  7 12 19:22 LEGACY_2D.vtk
-rw-r--r--@ 1 sugimoto  staff  2176434  7 12 19:22 XML_2D.vti

同じ内容でもXMLはファイルが小さく,なぜか倍精度8バイトx要素数 よりも小さくなります.ま,LEGACYの場合,例えば8桁しか書かないなど精度を下げればファイルは小さくなります.

時間発展データ

時間発展する計算では, 時々刻々のデータを可視化したいですよね!残念ながらVTK自身は空間座標\(\boldsymbol X\)の概念はありますが, 時刻\(t\)という概念がないです.ですが可視化ソフトParaViewには時間の概念があります.やり方は2通り

  • data001.vti, data002.vti, ...., data999.vti  とか連番になっているファイルは,多分時間発展なんだろうと憶測して data...vti として扱うことができます
    • その副作用で, Case001, Case002,... というフォルダーを「時間発展データだwww」と誤認してしまいます(ここの>test... ってやつです)
    • 時間の値\(t=23.44\)とかを設定できないです.123番目のファイルを\(n=123\)とかなら,できますけど
  • PVDファイルというのを作成すると,時刻を制御できます.

PVDファイルを作成するには, AIモンキーは「自作で頑張れ.おら,知らねえ」と,つれない返事でしたので, 流体力学研究室謹製PVDWrite.hppを添付しているのでダウンロードして入れてください.

PVDwriteの 使い方

PVDwriteは, 最初にPVDファイル名を指定しておきます.で, 時刻t_1のデータがFILE_1にあるのであれば, Append(FILE_1, t_1)します.で,どんどんAppend()してから最後に Write() すると, 指定した名前.pvd というファイルを,指定したフォルダーに作成します.

まあ使い勝手から言って, あるデータフォルダに

  • 指定した.PVD
  • Contents/ フォルダ
  • Contents/FILE_1   ノーマルなVTKファイル
  • Contents/FILE_2     ノーマルなVTKファイル
  • ...

というのが,使いやすいと思う.指定した.PVDファイルに,時刻の一覧と多応するファイルが書いてあるので,PVDファイルをParaviewに読み込ませれば動作する.

// main.cpp
#include <iostream>
#include "../include/vtk_myIO.hpp"
#include "../include/pvdwrite.hpp"
int T1()
{
   ...さっきまでの main.cpp のなかみ
}
int T2()
{
   std::filesystem::path myHome = getenv("HOME");  //このへんは,さっきと同じ
   auto myFolder = (myHome / "Data") / "test06";   //このへんは,さっきと同じ
   PVDwrite Indexer(myFolder/"test.pvd");
   vtk_myIO Writer;
   Writer.ox << -3, -3, 0;
   Writer.dx << 0.01, 0.01, 0.;
   Writer.nx << 600, 600, 1;
   double time=0.0, dt=0.01;
   auto FUNC = [&time,&Writer](int i, int j) -> double
   {
       double x = Writer.ox[0] + Writer.dx[0] * i + 0.5*std::cos(2*M_PI*time);
       double y = Writer.ox[1] + Writer.dx[1] * j + 0.5*std::sin(2*M_PI*time);
       double rate= 1.0 + 0.5 * std::sin(8 * M_PI * time);
       return std::exp(-x * x - y * y)*rate;
   };
   for(int nt=0; nt<100; nt++)
   {
       time = nt * dt;
       std::ostringstream oss;
       oss << "XML_2D_" << std::setw(5) << std::setfill('0') << nt << ".vti";
       Writer.filename = (myFolder / "Contents") / oss.str();
       Writer.write_xml_vti2d(FUNC);                 //さっきと同じようにVTKファイルを出力
       Indexer.Append(Writer.filename, time);
   }
   Indexer.Write();
   std::cout << "PVD file written: " << Writer.filename << std::endl;
   return 0;
}

PVDファイルのパス名を指定して初期化しましょう. 名前をIndexerにしています. 描く関数は, まあテストなんで, 時刻timeに依存する関数を定義してみた.で,各時刻について, VTKファイルを作成します.この辺は新顔ですね!これは, 「ふんぎゃあ0023.vti」とか,番号のついたファイルをたくさん作る時に使うものです. 

  • std::cout << "ほれほれ" では,画面に「ほれほれ」が送られて出力された.std::ofstream OFS であると,  OFS << "このやろう" で, ファイルOFSに「このやろう」が送られてファイルに出力された.
  • std::ostringstream MOJIだと,MOJI << "もじもじ"  によって, 「もじもじ」が,文字列MOJI に送られるのだ!
  • oss << "XML_2D_" によって文字列は XML_2D_ になった.std::setw(5)は,文字幅が5文字分になり,std::setfill('0') は,あきスペースは全部0で埋め尽くせ
  • そこで oss << nt << ".vti" によって,文字列はXML_2D_00232.vti になった!
  • こうやって作成した文字列は, oss.str() として利用できる.

そうやって作成した「ふんぎゃあ0023.vti」ファイル名と, 時刻の値を指定してIndexerにAppendで, 全時刻書き終わったら, IndexerでWriteします.すると,PVDファイルができます.

後学のために,PVDファイルの中身を見てみましょう(ただの目次のテキストファイルですので,普通にエディターで見える)

<?xml version="1.0"?><VTKFile type="Collection" version="0.1" byte_order="LittleEndian" compressor="vtkZLibDataCompressor" ><Collection>
<DataSet timestep="0" group="" part="0" file="Contents/XML_2D_00000.vti" />
<DataSet timestep="0.01" group="" part="0" file="Contents/XML_2D_00001.vti" />
<DataSet timestep="0.02" group="" part="0" file="Contents/XML_2D_00002.vti" />
<DataSet timestep="0.03" group="" part="0" file="Contents/XML_2D_00003.vti" />
<DataSet timestep="0.04" group="" part="0" file="Contents/XML_2D_00004.vti" />
...
</Collection></VTKFile>

なんかじゅげむじゅげむと書いた後データ(ファイル名と時刻の値)をただ並べているのがわかります.groupとpartは,現在意味不明です.二つのPVDwriteを足し合わせると, たしか新しいPVDファイルになって,片っ方がpart=0, 片っ方が part=1 になった気がします.

にゃあ

アニメーションの作成

動画を作成するには, FFMpegをインストールする必要があります.インストールが済んだら,ParaViewを起動し,作成したPVDファイルを読み込むと, こんな感じになります:

  • backgroundの色が面白いですが → サルが教えてくれます
  • WarpByScalarとかClipってなんですか  → サルが教えてくれます
  • 時刻が画面に出てますね  → サルが教えてくれます
  • 文字がTeXみたいに綺麗な字ですね → $t=$ とか書くと,TeX文字が使えます.詳細は猿に聞け
  • なんか画像が小さいっすね →  ビデオを作成するときのコツとして,「ありふれた画像サイズを使う」というのは重要です.
    • 動画を表示するのは,結構CPUパワーを消費します.
    • 世界のPC,スマートフォンなどは,ありふれた画像サイズは最適に表示できるように設計されています.摩訶不思議なサイズの動画は,最適にならず「カクカク」動作になることが多い
ありふれた動画サイズとは
SD SVGA Instagram HD
FaceBook
WXGA SXGA
640x480 800x600 1080x1080 1280x720 1280x800 1280x1024
HD+ FHD
1080p
YouTube
WQHD UHD
4K
5K 8K
1600x900 1920x1080 2560x1440 3840x2160 5120x2880 7680x4320

あまりへんちくりんな解像度にしない方が身のためです.そのために,ParaViewでは【View】【Preview】で画像サイズが選べます.

  • 繰り返しますが,動画の再生はPCには負担です.学術発表では一画面に数枚の動画を同時再生するとかもあり得ますので,解像度を無駄に高くしておくと,発表時に動作しない,カクカク動きになる,などのトラブルが起こります.
  • Intel-CPUならSVGA, AppleSilconでもHD程度で良いのでは.だってほれ,プロジェクターの解像度が本当にFHDだと百万円とかのレベルだし.普通はWXGA程度だ

【File】【Save Animation】

んで

作成したAVIファイルは, MotionJPEG形式になっています. PowerPoint, Keynoteなどには,そのまま使えますが,サイトにアップするとかではダメな場合も多いです.

お手軽に変更

Finderで【指定したビデオファイルをエンコード】で,MP4形式(H264)を選んで保存すれば, MP4ファイルになります(Appleは,なぜかMP4という名前が嫌いでMOVというファイルで作成するが,renameしてMP4に変更して,全く問題ない)

AppleがMOVファイルとして開発したのがMP4規格になったんで,なんで俺が後から変えなきゃならねえんだ,とAppleの意地があるそうだ.迷惑だが

 

詳細に変更

FFmpegで変更もできる.ターミナルで

myMac:test06 % ffmpeg -i save.avi -c:v libx264 -crf 23
 -preset medium -c:a aac -b:a 192k output.mp4

これで, save.avi (MJPEG) が output.mp4 (MP4)に変換される.

 

まあこんな感じになる:

 

VTK-StructuredGrid

極座標とかの

 

File attachments
添付 サイズ
VTK-9.4.2-1931-osx15.3.2.pkg 0 バイト
pvdwrite.hpp_.gz 1.06 KB
output.mp4 338.27 KB