python で pacsdb から dicom ファイルを閲覧

なんだか数年前に戻ってしまったような感じがしますが、dcm4chee のデータベース pacsdb の中身を読んでファイルの格納場所を探し、sftp でローカルにダウンロードして閲覧します。

これは pacs と呼べる代物ではありませんが、dicom ファイルを閲覧することはできます。

ホスト側の設定

サーバーのデータベースにアクセスするためには、サーバー側でクライアントをユーザー登録し権限を与える必要があります。

ユーザー登録と権限を与えます。

mysql> 
CREATE USER 'heno'@'192.168.0.8' IDENTIFIED BY 'moheno';
mysql> 
GRANT SELECT ON pacsdb.* TO 'heno'@'192.168.0.8';

/etc/mysql/mysql.conf.d/mysqld.cnf の設定変更。
以下のようにコメントアウトします。

/etc/mysql/mysql.conf.d/mysqld.cnf

#bind-address = 127.0.0.1

ポートを開放します。


sudo ufw allow 3306

再起動。


sudo ufw reload
sudo service mysql restart

クライアント側からアクセス

クライントからアクセスします。


mysql -h 192.168.56.101 -u heno -p

データベース確認。

mysql> 
show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| pacsdb             |
+--------------------+
2 rows in set (0.00 sec)

データベースからfile の保存場所を抽出

検査IDが 1 の dicom ファイルの保管場所を抽出するのは、


SELECT filepath 
FROM files 
WHERE instance_fk IN 
    (SELECT pk 
    FROM instance 
    WHERE series_fk IN 
        (SELECT pk 
        FROM series 
        WHERE study_fk IN
            (SELECT pk 
            FROM study
            WHERE pk = 1
            )
        )
    );

結果は、


+----------------------------------------+
| filepath                               |
+----------------------------------------+
| 2022/6/30/5/D60B419F/E8228881/6124F6E9 |
| 2022/6/30/5/D60B419F/E8228881/6124F9D5 |
+----------------------------------------+
2 rows in set (0.00 sec)

dicom ファイルの格納場所は「/var/www/html/DICOM」としたので、ファイルの絶対パスは「/var/www/html/DICOM/2022/6/30/5/D60B419F/E8228881/6124F6E9」となります。

python で抽出

外から pk を与えて、dicom ファイルを抽出します。


    def selectDfile( self, pk ):
        conn = pymysql.connect(host=self.host,
            user=self.user,
            db=self.db,
            password=self.password,
            cursorclass=pymysql.cursors.DictCursor)
        try:
            with conn.cursor() as cursor:
                sql = "SELECT filepath FROM files WHERE instance_fk IN (SELECT pk FROM instance WHERE series_fk IN (SELECT pk FROM series WHERE study_fk = %s));"
                cursor.execute( sql, pk )
                return cursor.fetchall()
            conn.commit()
        finally:
            conn.close()

ダウンロードディレクトリとweasisを動的に

dcm4hee などでは、weasis.jnlp がダウンロードされてきますが、dicom ファイルも /tmp にダウンロードされます。

それを真似て、dicom ファイルをダウンロードするディレクトリを「/tmp/dcmtmp/年月日+pk」とします。

そして、weasis が読み込むディレクトリを「/tmp/dcmtmp/年月日+pk」に動的に変更します。

まずは、「/tmp/dcmtmp/202207031」というディレクトリを作成。

同時に「/tmp/dcmtmp/202207031.jnlp」という jnlp ファイルをテンプレートから作成。
その jnlp ファイルが読み込む先を「/tmp/dcmtmp/202207031」にします。


    def createtmp( self, pk ):
        with open( self.template, encoding="utf8" ) as f:
            data_lines = f.read()        
        dt_now = datetime.datetime.now()
        self.tmpdir = '/tmp/dcmtmp/' + dt_now.strftime('%Y%m%d') + pk + '/'
        self.tmpweasis = '/tmp/dcmtmp/' + dt_now.strftime('%Y%m%d') + pk + '.jnlp'
        data_lines = data_lines.replace( "tmpdirectory", self.tmpdir )        
        os.makedirs( self.tmpdir, exist_ok=True )       
        with open( self.tmpweasis, mode="w", encoding="utf8" ) as f:
            f.write(data_lines)

jnlp のテンプレートは「/var/www/html/weasis/weasistemplate/weasisStart.jnlp」です。

/var/www/html/weasis/weasistemplate/weasisStart.jnlp

<?xml version="1.0" encoding="UTF-8"?>
  <jnlp spec="1.6+" codebase="http://localhost/weasis" href="">
  <information>
    <title>Weasis</title>
    <vendor>Weasis Team</vendor>
    <description>DICOM images viewer</description>
    <description kind="short">An application to visualize and analyze DICOM images.</description>
    <description kind="one-line">DICOM images viewer</description>
    <description kind="tooltip">Weasis</description>
  </information>
  <security>
    <all-permissions />
  </security>
  <resources>
    <!-- Requires Java SE 6 update 10 release for jnlp extension without codebase (substance.jnlp) -->
    <j2se version="1.6.0_10+" initial-heap-size="128m" max-heap-size="512m" />
    <jar href="http://localhost/weasis/weasis-launcher.jar" main="true" />
    <jar href="http://localhost/weasis/felix.jar" />
    <extension href="http://localhost/weasis/substance.jnlp" />
    <!-- Allows to get files in pack200 compression, only since Weasis 1.1.2 -->
    <property name="jnlp.packEnabled" value="true" />
    <!-- ================================================================================================================= -->
    <property name="jnlp.weasis.felix.config.properties" value="http://localhost/weasis/conf/config.properties" />
    <property name="jnlp.weasis.felix.extended.config.properties" value="http://localhost/weasis-ext/conf/ext-config.properties" />
    <property name="jnlp.weasis.weasis.codebase.url" value="http://localhost/weasis" />
    <property name="jnlp.weasis.weasis.codebase.ext.url" value="http://localhost/weasis-ext" />
    <property name="jnlp.weasis.gosh.args" value="-sc telnetd -p 17179 start" />
    <property name="jnlp.weasis.apple.laf.useScreenMenuBar" value="true" />
    <property name="jnlp.weasis.weasis.i18n" value="http://localhost/weasis-i18n" />
    <!-- ================================================================================================================= -->
  </resources> 
  <application-desc main-class="org.weasis.launcher.WebstartLauncher">
    <argument>$dicom:get -l tmpdirectory</argument>
  </application-desc>
  </jnlp>

下の方の「tmpdirectory」を「/tmp/dcmtmp/202207031」に編集して、この jnlp ファイルを「/tmp/dcmtmp/202207031.jnlp」という名前で保存します。

dicom ファイルを sftp でダウンロード

dicom ファイルの絶対パスはわかったので、それらのファイルをローカルの「/tmp/dcmtmp/202207031」に sftp でダウンロードします。


    def sftp_connection(self):
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
        self.client.connect(self.config['host'],
                       port = self.config['port'],
                       username = self.config['username'],
                       password = self.config['password'])
        sftp_con = self.client.open_sftp()
        return sftp_con

    def dcmdn( self, fps ):
        sftp_con = self.sftp_connection()
        for fp in fps:
            remote_path = '/var/www/html/DICOM/' + fp['filepath']
            local_path = self.tmpdir + os.path.basename( remote_path )
            sftp_con.get( remote_path, local_path )
        self.client.close()

weasis 起動

dicom ファイルは「/tmp/dcmtmp/202207031」にあり、それを読み込んで表示する jnlp ファイルは「202207031.jnlp」という名前で「/tmp/dcmtmp」にあります。

それを起動します。


    def weasis(self):
        subprocess.Popen('javaws ' + self.tmpweasis, shell=True)

全プログラム

selectpk.py

import os
import paramiko
import pymysql
import datetime
import subprocess

class DCM4CHE():

    def __init__( self ):
        self.host = '192.168.56.101'
        self.user = 'heno'
        self.password = 'moheno'
        self.db = 'pacsdb'
        self.config = {
            "host" : "192.168.56.101",
            "port" : 22,
            "username" : "user",
            "password"   : "pass"
        }
        self.tmpdir = ''
        self.template = "/var/www/html/weasis/weasistemplate/weasisStart.jnlp"
        self.tmpweasis = ''
            
    def selectDfile( self, pk ):
        conn = pymysql.connect(host=self.host,
            user=self.user,
            db=self.db,
            password=self.password,
            cursorclass=pymysql.cursors.DictCursor)
        try:
            with conn.cursor() as cursor:
                sql = "SELECT filepath FROM files WHERE instance_fk IN (SELECT pk FROM instance WHERE series_fk IN (SELECT pk FROM series WHERE study_fk = %s));"
                cursor.execute( sql, pk )
                return cursor.fetchall()
            conn.commit()
        finally:
            conn.close()
        
    def sftp_connection(self):
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
        self.client.connect(self.config['host'],
                       port = self.config['port'],
                       username = self.config['username'],
                       password = self.config['password'])
        sftp_con = self.client.open_sftp()
        return sftp_con

    def dcmdn( self, fps ):
        sftp_con = self.sftp_connection()
        for fp in fps:
            remote_path = '/var/www/html/DICOM/' + fp['filepath']
            local_path = self.tmpdir + os.path.basename( remote_path )
            sftp_con.get( remote_path, local_path )
        self.client.close()
        
    def createtmp( self, pk ):
        with open( self.template, encoding="utf8" ) as f:
            data_lines = f.read()        
        dt_now = datetime.datetime.now()
        self.tmpdir = '/tmp/dcmtmp/' + dt_now.strftime('%Y%m%d') + pk + '/'
        self.tmpweasis = '/tmp/dcmtmp/' + dt_now.strftime('%Y%m%d') + pk + '.jnlp'
        data_lines = data_lines.replace( "tmpdirectory", self.tmpdir )        
        os.makedirs( self.tmpdir, exist_ok=True )       
        with open( self.tmpweasis, mode="w", encoding="utf8" ) as f:
            f.write(data_lines)   
    def weasis(self):
        subprocess.Popen('javaws ' + self.tmpweasis, shell=True)
       
if __name__ == '__main__':
    pk = '2'
    ftptr = DCM4CHE()
    dcmfarr = ftptr.selectDfile(pk)
    ftptr.createtmp(pk)
    ftptr.dcmdn(dcmfarr)
    ftptr.weasis()