Matlabで進捗状況のテキスト表示

# 目的

- matlabで処理の進捗状況を表示する
- グラフィカルな`waitbar`関数は使わない
- コマンドスペースにテキスト表示
- 進捗状況を更新させることで，１行にすっきり収めたい 

※　本記事は`FieldTrip`[[1][ref1]]の`ft_progress`[[2][ref2]]関数の一部解説になります


# 背景
処理が止まっていないな，どのくらい処理が進んでいるかなどを確認したいときがある。組込関数の`Waitbar`を使うと処理が重くなることは有名な話で[[3][ref3]]，使用は避けたい。他の方法としては，`fprintf`でiteration毎にその`index変数`であったり`*`をコマンドウインドウに表示する手が考えられる。ただし，iteration回数が多いと（改行を入れても入れなくても）表示範囲が大きくなり，コマンドウインドウが強制的に（横または縦方向へ）スクロールされてしまう。
これにより，状況によっては以下の問題が発生する場合がある。

1. 視認性が悪く，進捗割合が直感的に分かりづらい
-> どの処理における進捗状況なのか全くわからない
1. 他に表示している情報（e.g., 警告など）が埋もれてしまう

具体的には，

```Matlab:CommandWindow
% 強制横スクロールされるパターン
> Current trial: 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 30 31 32 33 34 45 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
```

```Matlab:CommandWindow
% 改行を挟むパターン。
> Please wait...
> trial  1
> trial  2
> trial  3
> trial  4
> trial  5
> trial  6
> trial  7
> trial  8
> trial  9
> trial 10
> trial 11
> trial 12
```

```Matlab:CommandWindow
% アスタリスクを5個表示していくパターン
% 5個ごとにまとめると若干見やすいが，全体の割合情報を示す方法がない。
> Please wait...: ***** ***** ***
```

じゃあどうするか。
良い例として，脳波解析用ツールボックス`FieldTrip`[[1][ref1]]で実装されている方法がある。
表示内容としては以下のようになる。

```Matlab:CommandWindow
> Please wait... 32/100 
```

実際には，改行なしで数字部分が更新される（e.g., 1/100　→　2/100）。
これがどのように実行されているかを確認したので，覚書のついでにまとめる。
本記事はFieldTripの`ft_progress`[[2][ref2]]関数の一部の解説になるので，詳細はそちらを参照いただきたい。

# 方法解説

結論： **N-1回目の表示を消してからN回目の状況を表示すれば良い**

`ft_progress`[[2][ref2]]のコードを確認していく。

```Matlab:ft_progress(varargin)
% FT_PROGRESS shows a graphical or non-graphical progress indication similar to the
% standard WAITBAR function, but with the extra option of printing it in the command
% window as a plain text string or as a rotating dial. Alternatively, you can also
% specify it not to give feedback on the progress.

（略）

% Copyright (C) 2004-2008, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%

（略）

        % ensure the string does not end with a newline or carriage return
        % either would break compatibility with a -nodesktop matlab
        % environment
        if length(varargin{2})>1 && (all(varargin{2}((end-1):end) == '\r')...
            || all(varargin{2}((end-1):end) == '\n'))
          varargin{2} = varargin{2}(1:end-2);
        end
        
        % strlen分だけ，formatSpecの前に特殊文字のバックスペースを追加する
        % strlenは永続変数で，初期宣言時は最初は０が代入されている
        % fprintfで進捗状況の表示後，その文字列の文字数が代入されることになる
        % つまり，N-1回目で表示した文字列分だけバックスペースで削除してから新規で文字列表示する
        varargin{2} = [repmat(sprintf('\b'),[1 strlen]) varargin{2}];

        if usejava('desktop')
          % a newline is appropriate when using the desktop environment
          varargin{2} = [varargin{2} '\n'];
        end
        
        % formatSpecと書式演算子への代入値をpart1変数に格納し，fprintfに引き渡す
        part1 = sprintf(varargin{2:end}); 
        fprintf(part1);

        % 表示した文字列の文字数を永続変数のstrlenに保管しておく
        strlen = length(part1) - strlen;

（略）

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% some test code follows
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

（略）

% ft_progress('init', 'text');   
% for i=1:100
%     ft_progress(i/100, '%d/%d'  , i, 100);
% end
% ft_progress('close')
      
```

`ft_progress`の使い方として，上記スクリプトの最下部にtest codeとしてUSAGEの記載があるため参照のこと。まずinitializeのために`ft_progress('init', 'text');`とcallしておく必要があるが，ここでは詳細の説明は割愛する。for文の中で実際に進捗状況を表示するときのcallは，`ft_progress(i/100, '%d/%d'  , i, 100);`となるので，上記スクリプトでの入力引数`varargin`の内容はは以下のようになる。

- varargin{2}: `fprintf`[[4][ref4]]における`formatSpec`
- varargin{3}: for文の現在のindex
- varargin{4}: Iteration回数

最初のif分では，`formatSpec`が改行(\n)またはキャリッジターン(\r)で終わっている場合はそれを削除する内容になっている。これは，キャリッジリターンを文末に書き加える処理が後に続くため，事前に入っている場合は取り除いておくという動機である。

次に，`varargin{2} = [repmat(sprintf('\b'),[1 strlen]) varargin{2}];`によって，「指定された`formatSpec`の前に`backcpace`(\b)を`strlen`個だけくっつける」処理がなされる。`strlen`は，上記にピックアップしたスクリプト内では宣言されていないが，`ft_progress('init', 'text')`の実行時に初期値`0`の永続変数として宣言されている。Initializationの後に最初の進捗表示が行われたら，その表示文の文字数が格納される。

２回目以降の呼び出しでは，先の表示文の文字数分だけのバックスペースが文頭に付け加えられる。これにより，前回の表示内容を消してから新たに表示を行う。表示されるテキストの変更点としてはiteration indexの`i`だけなので，そこだけ更新されているように見えるという仕組みである。

# スクリプト例

`ft_progress`関数から必要な処理だけ引っ張ってくると以下のような内容になる。


```Matlab:show progress
strlen =  0;  % 表示する文字列の長さを記録する変数
iteNum = 20;  % 適当に繰り返す試行数
fprintf('Pleese wait .........\n');
for I = 1:iteNum
    Tmp = {'Current trial: %3d/%d\n', I, iteNum};
    Tmp{1} = [ repmat(sprintf('\b'),[1 strlen]),  Tmp{1} ];

    Txt = sprintf(Tmp{1:3});
    fprintf(Txt);
    strlen = length(Txt) - strlen;
    
    pause(0.1); % 何かしらの計算処理。ここでは単にポーズをいれている。
end

fprintf('................ done\n');
```

以上

# 参考文献
1. [FieldTrip][ref1]
1. [ft_progress - FieldTrip][ref2] 
1. [MATLABのwaitbar()はできるだけ避けたほうが良い理由-Qiita][ref3]
1. [fprintf - MATLAB][ref4]

[ref1]:https://www.fieldtriptoolbox.org
[ref2]:https://github.com/fieldtrip/fieldtrip/blob/master/utilities/ft_progress.m
[ref3]:https://qiita.com/kouichi-c-nakamura/items/179e9debc4606a57bd86
[ref4]:https://jp.mathworks.com/help/matlab/ref/fprintf.html#btf8xsy-1_sep_shared-formatSpec