またまたバカやっていた

今朝病院に出勤する自転車を漕ぎながらふと疑問が湧きました それは

JPEG Losless DC成分で用いるハフマン符号は、12 bitsの差分値まで対象としている
それであれば、1 pixel = 8 bitsと固定していてはいけないのではないか?

というものでした。これまでは、DICOM XAではどうせ1 pixel = 8 bits固定と考えていたのですが、もちろそれは大多数の場合正しいのですが、本当に良いのでしょうか?

そこで再び DICOM規格書に戻ると、これはDICOM tabでbits allocatedなどで何ビットを一ピクセルに指定するか? それが指定されているのです。

そこで、DICOM tagを検索したのです。これもSterlingで行いましたが、無い! 無い! そんなタグ無い!でした。いやいやそんな訳無い、と思い、今度はVisualStudioでbinary openして調べて気が付きました。結局、little endianで並んでいるので、0x00 0x28で検索してはヒットしません。0x28 0x00で検索せねばならなかったのです。まったくバカしていましたね。

一日中トラブル処理

昨日はまず、VisualStudio 2010でコンパイルしてもリンカーがエラーを起こす、というトラブルから始まりました。色々調べるとどうやら .NET Framework 4.5がインストールされ、4.0が消えてしまったからのようです。どうしてそんなことになったのか? と言えば、Microsoftのサイトからフリーで今downloadできる、VisualStudio 2012RCをインストールしたからのようです。

結局、.NET Framework 4.5をアンインストールしてから、再度2010をインストールしてこのトラブルを解決、

そうこうしている内に、MacBook ProにインストールしてあるParallels Desktopを立ち上げたところ、HD1が見つからない、従ってWindows7も立ち上げられない、というトラブルに遭遇、これは焦りました。全て消えたのか? です。

少し考えてから、Timemachineを立ち上げ、8/14にあったバック・アップから、Desktopに存在したWindows7.vpmといかうものをリカバーしたところ、解決しました。しかし、それからWindows Updateをしたり、色々で、結局数時間も使うはめになりました。

ビットストリーム (Bit Stream)入力クラス

以前(2008年頃)書いたプログラムを引っ張り出しました。そしてバグを発見したので、少し書きなおしました。もうバグは無いと思いますが・・・

  1. //
  2. // ReadBitStream Class
  3. //// unsigned char * bpBufに sizeバイト置かれたメモリーブロックから
  4. //// bit単位で値を取り出すクラス
  5. //// エラーがあれば、throwするので、読みだす方は try ~~ catch すべき
  6. //// modified on August 25th, 2012 
  7. //// by Shigeru SAITO, MD, FACC, FSCAI, FJCC
  8. //// originally created by Shigeru SAITO in 2008
  9. //
  10.  
  11. #ifndef __READBITSTREAM_H_
  12. #define __READBITSTREAM_H_
  13.  
  14. #include <memory.h>
  15. typedef unsigned char BYTE;
  16. typedef unsigned short WORD;
  17.  
  18. class CReadBitStream
  19. {
  20. private:
  21.  
  22. protected:
  23.     BYTE *mbpRead;            // 読み出しアドレス
  24.     BYTE *mbpEndOfBuf;        // Bufferの終了アドレス
  25.     BYTE mMask;               
  26.  // bit maskであると同時に現在の読み出しビット位置 (MSB = 7, LSB = 0)
  27.     bool mReadable;            // 1: 読み出し可、 0: 読み出し不可
  28.     void IncBuf(void);        
  29. // 読み出しアドレスのインクリメントとアクセス違反のチェック
  30. public:
  31.     CReadBitStream(void);
  32.     CReadBitStream(BYTE *bpBuf, int size);    // 唯一のconstructor
  33.     virtual ~CReadBitStream(void);
  34.     BYTE GetBYTE(void);        // 1 BYTE読み出し
  35.     WORD GetWORD(void);        // 1 WORD読み出し
  36.     void CopyBYTEs(BYTE* bpDest, int n);        
  37. // n BYTEs読みだしてbpDestのアドレス以降にコピーする
  38.     void SkipBYTE(int n);                    // n BYTEs読み飛ばし
  39.     int GetBit(void);                        // 1 bit読みだして返す
  40.     int GetBits(int numOfBits);                // numOfBits数のビットを読みだして返す
  41.     BYTE* GetNextAddress(void);                // 次の読み出しアドレスを戻す
  42.     int ResetBuffer(BYTE* bpBuf, int size);    // bufferを変更する
  43. };
  44.  
  45. #endif __READBITSTREAM_H_

 

以上がheader部分です、そして.cppの方は以下です

  1. #include “ReadBitStream.h”
  2.  
  3. CReadBitStream::CReadBitStream(void)
  4. :mbpRead(0), mbpEndOfBuf(0), mMask(0), mReadable(false) {}
  5.  
  6. CReadBitStream::CReadBitStream(BYTE *bpBuf, int size) {
  7.     mbpRead = bpBuf;                        // 読み出しアドレスをbufferの先頭にセット
  8.     mbpEndOfBuf = mbpRead + size;            // bufferの最終アドレスをセット
  9.     // 状態変数の初期化
  10.     mMask = static_cast<BYTE>(0x80);        // setMSB
  11.     mReadable = true;                        // アクセスエラー無し
  12. }
  13.  
  14. int CReadBitStream::ResetBuffer(BYTE *bpBuf, int size) {
  15.     mbpRead = bpBuf;
  16.     mbpEndOfBuf = mbpRead + size;
  17.     if ((bpBuf == 0)||(size == 0)) {
  18.         mReadable = false;
  19.         return 1;
  20.     }
  21.     mMask = static_cast<BYTE>(0x80);
  22.     mReadable = true;
  23.     return 0;
  24. }
  25.  
  26. CReadBitStream::~CReadBitStream(void) {
  27.     mbpRead = 0;
  28.     mbpEndOfBuf = 0;
  29. }
  30.  
  31. BYTE CReadBitStream::GetBYTE(void) {        // 1 BYTE読み出し
  32.     if (mReadable) {
  33.         if (!(mMask > 0x80)) {                // BYTE途中は不可
  34.             IncBuf();                        // 次のBYTEに進める
  35.             mMask = static_cast<BYTE>(0x80);
  36.         }
  37.         BYTE r = *mbpRead;                // 実際に1 BYTE読みだす
  38.         IncBuf();
  39.         return r;
  40.     } else {
  41.         throw(“Error at GetBYTE”);
  42.     }
  43. }
  44.  
  45. WORD CReadBitStream::GetWORD(void) {        // 1 WORD読み出し
  46.     if (mReadable) {
  47.         if (!(mMask > 0x80)) {                // BYTE途中は不可
  48.             IncBuf();
  49.             mMask = static_cast<BYTE>(0x80);
  50.         }
  51.         WORD r = (static_cast<WORD>(*mbpRead))<<8;    
  52. // 1 BYTE読み出しそれをWORDにcastしてから8bits左シフト
  53.         IncBuf();
  54.         r |= static_cast<WORD>(*mbpRead);    
  55. // さらに1 BYTEを下のBYTEに付加する
  56.         IncBuf();
  57.         return r;
  58.     } else {
  59.         throw(“Error at GetWORD”);
  60.     }
  61. }
  62.  
  63. void CReadBitStream::CopyBYTEs(BYTE* bpDest, int n) {    
  64. // n BYTEsをpbDestに一挙にコピーしてしまう
  65.     if (mReadable) {
  66.         if (!(mMask > 0x80)) {            // BYTE途中は不可
  67.             IncBuf();
  68.             mMask = static_cast<BYTE>(0x80);
  69.         }
  70.         if (mbpRead + n > mbpEndOfBuf) {
  71.             throw(“Error at CopyBYTEs1”);
  72.         }
  73.         memcpy(bpDest, mbpRead, n);
  74.         if ((mbpRead += n) >= mbpEndOfBuf) mReadable = false;    
  75. // 次の読み出しは不可
  76.         return;
  77.     } else {
  78.         throw(“Error at CopyBYTEs2”);
  79.     }
  80. }
  81.  
  82. void CReadBitStream::SkipBYTE(int n) {            // n BYTEs読み飛ばす
  83.     if ((mbpRead + n) > mbpEndOfBuf) {
  84.         throw (“Error at SkipBYTE”);
  85.     }
  86.     if ((mbpRead += n) >= mbpEndOfBuf) mReadable = false;    
  87. // 次の読み出しは不可
  88.     mMask = static_cast<BYTE>(0x80);
  89.     return;
  90. }
  91.  
  92. int CReadBitStream::GetBit(void) {                // 1 bit読みだす
  93.     if (mReadable) {
  94.         int r;
  95.         r = (*mbpRead > mMask) ? 1:0;
  96.         mMask >>= 1;
  97.         if (mMask == 0x00) {
  98.             mMask = static_cast<BYTE>(0x80);
  99.             IncBuf();
  100.         }
  101.         return r;
  102.     } else {
  103.         throw(“Error at GetBit **”);
  104.     }
  105. }
  106.  
  107. int CReadBitStream::GetBits(int numOfBits) {    
  108. // numOfBits読みだす (0 < n <= 16)
  109.     if ((numOfBits <= 0)||(numOfBits > 16)) {
  110.         throw(“Error at GetBits of numOfBits”);
  111.     }
  112.     if (!mReadable) throw(“Error at GetBits Initial”);
  113.     int r = 0;
  114.     while (numOfBits) {
  115.         if (mReadable) {
  116.             if (mMask == 0x00) {
  117.                 mMask = static_cast<BYTE>(0x80);
  118.                 IncBuf(); 
  119.             }
  120.             r <<= 1;
  121.             r |= ((*mbpRead > mMask) ? 1: 0);
  122.             mMask >>= 1;
  123.             numOfBits–;
  124.         } else {
  125.             throw(“Error at GetBits”);
  126.         }
  127.     }
  128.     return r;
  129. }
  130.  
  131. void CReadBitStream::IncBuf(void){                           
  132.  // buffer読み出しを1 BYTE進める
  133.     if (++mbpRead >= mbpEndOfBuf) mReadable = false;       
  134.  // 次は読み出し不可にする
  135. }

どうでしょうか? 自分で書いたテスト・プログラムでは大丈夫なようです。エラーがあれば、きちんとthrowされています。

キャストだらけのプログラム

型制約の厳しい言語では必要に応じて強制的な型変換(キャスト: Cast)が必要となり、それがまたバグの温床であるらしいのです。

C言語では簡単に型を(  )でくくるにことにより、このキャストが可能です。しかし、これはあまりにも簡単過ぎ、安易に行ってしまいますし、後でバグとりの時に発見しにくい、という副作用もあるらしいのです。

これに対して、C++ではややこしい構文をわざと使用せねばなりません。これにより、プログラマに対して心理的圧迫を与え、何とかしてキャストを使用せずにもっとスマートなプログラムを書くように暗黙的に強制します。また、バグ発見もこの構文を探すことにより、より容易にできる、このような利点が言われています。

こんなこと、自分ごときが書く小さなプログラムでは関係無いよ、そのように今まで思っていましたが、先の、DHTをサーチして、そのアドレスを可変配列Vectorに収納し、後から、そのアドレス近傍の内容を表記するプログラムを書いた時に、そのキャストの嵐に会いました。

  1.     std::vector <u_short *> address;
  2.     // u_charへのポインタをいれるvectorを宣言した
  3.     u_short *buffer = new u_short[bufSize/2]; 
  4.     // ワードずつ確保なので半分
  5.     u_short * const bufTop = buffer;
  6.     u_short * const bufEnd = bufTop + bufSize/2;
  7.     u_short *bufPntr = bufTop;
  8.     // 動き回るポインターを先頭にセット
  9.     fp.read(reinterpret_cast<char *> (buffer), bufSize);
  10.     // ここではバイト単位でしか指定できないのでキャストする
  11.     while (bufPntr < bufEnd) {
  12.         if (*bufPntr == 0xC4FF) {
  13.             // Little Endianに注意!!
  14.             // DHT tagにヒットした
  15.             address.push_back(bufPntr);
  16.         }
  17.         bufPntr++;
  18.     }
  19.     fp.close();
  20.     u_char * bufferByte;
  21.     // これでvector<u_short *> addressにDHTアドレスが格納された
  22.     std::vector<u_short *>::iterator it;;
  23.     for (it = address.begin(); it != address.end(); ++it) {
  24.         std::cout << std::hex << (int)*reinterpret_cast<u_char *> (*it) << ” “;
  25.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 1)) << ” “;
  26.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 2)) << ” “;
  27.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 3)) << ” “;
  28.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 4)) << ” “;
  29.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 5)) << ” “;
  30.         std::cout << static_cast<int>(*(reinterpret_cast<u_char *> (*it) + 6)) << ” “;
  31.         std::cout << (int)*(reinterpret_cast<u_char *> (*it) + 7) << ” “;
  32.         std::cout << (int)*(reinterpret_cast<u_char *> (*it) + 8) << ” “;
  33.         std::cout << (int)*(reinterpret_cast<u_char *> (*it) + 9) << ” “;
  34.         std::cout << (int)*(reinterpret_cast<u_char *> (*it) + 10) << std::endl;
  35.         std::cout << ” : “ << (int)*(bufferByte + 4) << std::endl;
  36.     }

こんなことになると、流石に自分の書いているプログラムは改良の余地がたくさんあるぞ、ということを思い知らされますよね。要するに、バイト単位で動かねばならないところを、わざわざワード単位で書いているからこんなことになるのでしょう。流石に31行から35行ではたまらなくなり、C言語のキャスト構文 (int)を static_cast<int>のかわりに使ってしまいました。
これもC++の新しいキャスト構文のお蔭様様ですかね。

これから羽田空港

今 羽田空港に移動中です。途中病院に寄り、昨日の TAVIの患者さん達の具合を伺ってきました。お二人ともお元気で、この治療法のすばらしさがまたしても証明されました。

昨日技術的に学んだことですが、我々が使用しているシステムは Self-expandableのものです。もちろんシースによりプロテクトされており、体温により温まった Nitinol合金姓のステントが大動脈基部で自然に拡張することにより、固定されます。頭の中では、その部位でシースを抜けば良い、これだけです。簡単なことです。

しかし、患者さんの体内では話が異なります。

  1. 心臓が強く拍動している – これに対しては rapid pacingである程度対応します
  2. 大腿動脈アプローチの場合には、長い経路、しかも大動脈弓で強く屈曲しているため、その部位で保護シースと大動脈との摩擦抵抗が大きい、しかもこれも心拍動により変化する
  3. 同様の理由により保護シースとステントの間の摩擦抵抗も大きい
  4. 同様の理由によりシステムと、stiff wireとの間の抵抗も大きい

実際の植え込みでは、これらの相反するいくつもの効力に逆らって指摘部位で植え込みを行わねばならないのです。そながら作用反作用をうまいこと魔法のようにコントロールすることが必要なのです。これにはどうやらセンスと経験が必要な気がします。それにようやく気が付いたのです。これはPCIとは全く異なる世界です。術者として技術的な部分で非常に惹かれる部分がありますし、自分にとってのチャレンジでもあります。

DICOM XAハフマン解析

DICOM XAのファイルを Binary Editorで調査すると どうやら 一コマ静止画の集まりのようです。


以上のように、SOIに始まり、内部にDHTを含み、それに引き続いてSOF3があり、最後にEOIで終わるブロックが一コマとなります。

ちなみに、JPEG規約により、soi = 0xFFD8, DHT = 0x FFC4, SOF3 = 0xFFC3, EOI = 0xFFD9なのです。ちなみに、それぞれ Start of Image, Define Huffman Table, start of Frame 3 = Lossless, End of Imageのことです。

手持ちのDICOM XA fileを調べると、SOIからEOIのブロックに 149.646KBぐらいが含まれていました。1 pixel = 8 bitsの濃度とすれば、1 Frame = 512 x 512 pixelとすれば、262.144 KBのデータ量ですので、42%までデータが圧縮されたことになります。

プログラムとしては、SOF3の次から、EOIの前までをイメージ圧縮データと考え、DHTより求めた解読表に沿って解読すれば良いことになりそうです。

JPEGハフマン解析

さて、少し回り道をしています。JPEG解析を行っているのは、あくまでもDICOM XAを解読するためなのです。DICOM XAはシネ画像の動画ですが、いきなり動画は敷居が高すぎます。従って、DICOM XAより抽出した一コマをまともな画像として表示することをまずは第一目標に掲げます。

さて、現段階で何処まで来たでしょうか?

  1. DICOM XAをメモリーに読む込むことができる
  2. 読み込んだメモリーブロックから DHT tagを検出することができる
  3. DHTからハフマン符号を解読することができる

以上でしょうか 従ってこれから行うことは、これらを連携し、さらにメモリーブロックからビット入力して、実際の絵に解読することです。

道は相当に遠いですね。

ハートチーム (Heart Team)

最近、心臓領域の医学界において、ハートチーム (Heart Team)という言葉が声だかに叫ばれています。この言葉は、もともとヨーロッパ心臓病学会、ヨーロッパ心臓外科学会などが共同で提唱した言葉です。一人の心臓病で苦しんでおられる患者さんを治療するに際しては、循環器内科とか心臓外科とかの単科医師が治療方針を決めるのではなく、共同で最適な治療方針を決めねばならない、というものです。そして、拡大した概念としては、これらの診療科医師のみならず、例えば一般内科医、呼吸器内科医、リハビリ診療医師、地域のかかりつけ医師などのみならず、関連するコメディカル、あるいは在宅ケアチームなどがあわさって一人の患者さんのために治療方針を協議して、決定するというものです。

もちちろん、重要なのは患者さん自身の人生観であり、またそのご家族のご意向もあります。

このようにして治療方針を決定し、後はそれを実行する医師が粛々と全力で治療に当たるのです。これは素晴らしい、当たり前だ、そのように誰しも思われると思います。

もちろんそうです。しかし単純に考えて、それを実現するためには、社会が1名の患者さん : 10 – 100名の医療関係者、という関形式を受け入れ、そのシステム維持構築に対して、支出することを許容する必要があります。また、方針決定に関して、実際に治療に当たる医師が全責任を負う必要が無くなるかも知れません。

うーん こう考えると難しい問題ですね。確かに、個々の医師に治療方針決定を任せてしまえば、独善的な治療がまかり通ってしまいます。その中には医学的におかしい治療もなされることでしょう。これは問題です。

ですから、現実にはこれを提唱しだしたヨーロッパでも、全部の患者さんに対してこのハートチームという概念で運用することを主張している訳ではありません。実際の治療として、医学的に完全には立証されていない治療、たとえば左主幹部病変に対する薬剤溶出性ステント植え込み治療とか、糖尿病を有する三枝病変に対する経皮的冠動脈インターベンション治療とか、そのような患者さんに限定して、ハートチームによる意思決定を呼び掛けているのです。

そして、もう一つ重要な疾病治療として、重症大動脈弁狭窄症患者さんに対する TAVI (経カテーテル的大動脈弁植え込み術: 最近では、特に外科系から、TAVR: 経カテーテル的大動脈弁置換術 という呼び名も好んで用いられます)なのです。

当院で、TAVIの治験を開始することに決まった時、僕は色々なことを考えました。

  1. そのような先進的医療を治験で行えることは自分自身とても光栄である
  2. これまで治験に先進的に意欲的に取り組んできたことが生かされた
  3. 世界の最先端医療に乗り遅れることなく、自分のキャリアが追従できる
  4. 開院以来25年間も経たない当院が、このような先進的治療を率先して行える立場になったことは本当に素晴らしい

などのポジティブな感想、そしてその一方でネガティブな感想というか不安

  1. 当院のような純然たる民間病院、何の冠もついていない民間病院で、そのような先進的医療を行うことは概念的に可能だろうか?
  2. そもそも当院にはこのような先進医療を行うだけの様々なリソースがあるのか?
  3. 病院の姿勢として、このような先進医療遂行を受け入れるものだろうか?
  4. 対象となる患者さんが当院のような民間病院に集まるであろうか?

などなどです。そして、そのような不安の中から思った結論は一つ

このような先進的医療を当院のような無冠民間病院が、大学病院や、国立センターなどに伍して行っていくためには、それらの病院を上回る総合力が必要である

ということでした。そして自然に思いついた概念が ハートチームだったのです。

本日二例目はあっという間に治療が大成功に終了しました。そして、患者さんを手術場から ICUに搬送するまでの皆を 治療成功の余韻を味わいながら見ていました。この一人の患者さんの治療のために、循環器科医師4名、心臓外科医2名、血管外科医1名、麻酔科医2名、エコー指導医1名、エコー検査技師2名、レントゲン技師2名、臨床工学士2名、看護師2名、CRC(臨床試験コーディネーター) 2名、その他5名そして外国からの指導医1名、ざっと数えただけでも、これだけ多数の人々が協力して当たったのです。そして、驚くべきことに、皆が楽しみ喜びながら、その準備や後片付けに当たっているのです。チームという言葉で片付けてしまうにはあまりにも深い連帯感がそこにはあるのです。

TAVIを開始し、色々なことを学んでいます。そして、純粋に医学的、あるいは医療技術的なこと以外にも、このようなチームとは? とか そのようなことについても学んでいます。TAVIを開始して、患者さんに対してはとても良いのですが、それ以上に自分自身にとって、本当に素晴らしい体験です。

しかし、本日の二例目、僕の中では壁を越えた感覚があります。これまで数々の新しい医療技術を伴う手技を経験克服してきました。それらの中である時、壁を越えた感覚を味わうことができます。本日はそのような日でした。

 

 

やはり Exciting!!

本日は朝から TAVIです。日本人の Intervetional Cardiologistとしてはトップ・レベルの経験を積みつつある私達ですが、毎回毎回学ぶことが多々あります。

そして何より思うのは、心の強さの必要性です。瞬時の判断力と、それを可能にする心の強さが一番重要な気がします。その次に必要なのは経験でしょう。もちろん、これらの前提には医師としての使命感とか、カテーテル技術の洗練度が必須条件としてあります。

昼の breakの後、また午後あります。最善を尽くし、多くの患者さんに役立てたいと思います。