Python.NET を使用する ZOS-API

この記事では、Python for .NET (pythonnet) を使用して ZOS-API と通信する方法を紹介します。 ZOS-API は .NET Framework を使用して作成されているので、.NET と直接通信できる言語を使用することで最大限の柔軟性とパフォーマンスを得ることができます。

著者 Michael Humphreys, Sandrine Auriol

Introduction

この記事では、Python for .NET の使用方法を説明し、Python を ZOS-API に接続する形態として、pythonnet モジュールを通じて .NET を使用する方法と、pywin32 モジュールを通じて COM を使用する方法との大まかな相違点を取り上げます。

.NET に Python を使用する理由

OpticStudio 20.1 までは、Python を ZOS-API に接続する方法として COM を使用してきました。この方法は、インターフェイス、クラス、オブジェクト、メソッドを Windows のレジストリに登録する必要があり、技術としては時代遅れです。この方法では、Python と ZOS-API とのすべての通信が、サード パーティ製のモジュールである pywin32 により、中間層を経由して変換されています。この技術では、ZOS-API.NET が持つ次のような利点を十分に活用できません。

  • 継承 :Inheritance: pywin32 では、この欠点を回避するために、継承メソッドである CastTo() を使用しています。
  • 列挙した変数 :ZOS-API では、唯一の列挙型辞書として constants を使用します。
  • インターフェイス :Windows では、インターフェイス、クラス、オブジェクト、メソッドを登録できるのは 1 回限りです。したがって、Windows のレジストリにオブジェクトを登録せずに、2 つのバージョンの OpticStudio を切り換えながら ZOS-API を実行してテストすることができません。

ZOS-API は .NET Framework を使用して作成されているので、.NET と直接通信できる言語を使用することで最大限の柔軟性とパフォーマンスが得られます。この新しいテンプレートでは、pywin32 モジュールを使用して Python を ZOS-API に接続するのではなく、pythonnet モジュールを使用してこの接続を実現しています。

この方法には、次のような利点があります。

  1. オブジェクトが自動的に継承されるので、CastTo() を使用する必要がありません。
  2. 列挙型が適切に処理されるので、constants 辞書を使用する必要がありません。
  3. C# や MATLAB を使用する場合とまったく同様に、2 つのバージョンの OpticStudio を切り換えながら ZOS-API を実行してテストできます。
  4. 大規模なデータ セットをバッチ処理する外部 DLL を使用できます。たとえば、シーケンシャル光線追跡や ZRD ファイルの解析で "RayTrace.dll" を使用できます。

例として、"PythonStandalone_01_new_file_and_quickfocus.py" で COM を使用したコードを以下に示します。

#! [e01s08_py]
# QuickFocus
quickFocus = TheSystem.Tools.OpenQuickFocus()
quickFocus.Criterion = constants. QuickFocusCriterion_SpotSizeRadial
quickFocus.UseCentroid = True
quickFocusCast = CastTo(quickFocus,'ISystemTool')
quickFocusCast.RunAndWaitForCompletion()
quickFocusCast.Close()
#! [e01s08_py]

.NET を使用して同じ処理を実行するコードを以下に示します。

#! [e01s08_py]
# QuickFocus
quickFocus = TheSystem.Tools.OpenQuickFocus()
quickFocus.Criterion = ZOSAPI.Tools.General.QuickFocusCriterion.SpotSizeRadial
quickFocus.UseCentroid = True   
quickFocus.RunAndWaitForCompletion()
quickFocus.Close()
#! [e01s08_py]

 

インストール

Python をインストールしていない場合は、まず記事「Python を始めよう」をご覧ください。

pip を通じて pythonnet モジュールをインストールする手順は以下のとおりです。

  1. 実行している Python のバージョンが 3.4、3.5、3.6、または 3.7 であることを確認します。現在のところ、Python 3.8 では pythonnet が動作しません。
  2. pip からインストールした pythonnet をインストールするか、それを既にインストールしていることを確認します。

コマンド プロンプトのウィンドウを開き、次のように入力します。

python -m pip install pythonnet

以下のメッセージが表示されれば、pythonnet がインストールされています。

 

Command

 

モジュールのインポート

必要なモジュールは clr モジュールのみです。このモジュールを使用すると、共通言語ランタイム (CLR) の各種名前空間を事実上 Python パッケージとして扱うことができます。CLR は、.NET プログラム コードの実行時環境です。

このほかに有用なモジュールとして numpy と matplotlib があります。

  • 配列データを、たとえば DLL から Python に渡す場合に numpy が便利です。
  • データをプロットするには、matplotlib を使用する方法が最も容易です。

どちらも、pip から手動でダウンロードできる補足モジュールです。なお、numpy は matplotlib と従属関係にあるので、次のように指定して matplotlib をインストールすると、両方のモジュールがインストールされます。

python -m pip install matplotlib

numpy に配列データを効率的に渡すには、メモリからデータを直接読み取る必要があります。したがって、ctype、sys、GCHandle、および GCHandleType のインクルードが必要です (OpticStudio で Python for .NET 向けに生成したテンプレートには、これらが既に用意されています)。Python ファイルのインポート セクションは次のようになります。

# THESE ARE REQUIRED FOR PYTHONNET
# common language runtime for PythonNET
import clr
# used to read memory address from .NET arrays and pass into NUMPY arrays
import ctypes, sys
from System.Runtime.InteropServices import GCHandle, GCHandleType

# THIS IS ALL-BUT-REQUIRED FOR PYTHON VISUALIZATION
# Python visualization and matrix manipulation
import matplotlib.pyplot as plt, numpy as np

# THESE ARE ONLY NEEDED FOR THIS SCRIPT
import os, winreg

つづいて、ZOS-API ライブラリなどの DLL が以下の行で参照として追加されます。

crl.AddReference()

 

列挙型でのPython の制限語の使用

Python の制限語を使用している列挙型が ZOS-API に存在することがあります。Python の制限語とは、Python で意味と構文が事前定義されている語です。構文がハイライト表示される IDE を使用している場合は、このような制限語の全体を入力すると、その文字色が変わることが普通です (多くの場合は青色になります)。その場合、その Python の制限語は使用できません。

Python の制限語として "None" がありますが、ZOS-API の列挙型の中には "None" がオプションとして用意されているものがあります。この問題の対策として、システムの名前空間から Enum クラスをインポートして、Enum.Parse() を使用します。

from System import Enum
win_settings.Polarization = Enum.Parse(ZOSAPI.Analysis.Settings.Polarization, "None");


複数の "出力" 変数の受け渡し

Python for .NET を使用する際の注意事項として、メソッドの引数が、そのメソッドの入力であるか出力であるかを Python for .NET では認識できないという点があります。したがって、"出力" 変数を持つメソッドを使用する場合、その関数を Python から呼び出すときに、これらの変数ではダミーのプレースホルダを使用する必要があります。

たとえば、C# で次のメソッドを使用するとします。

public bool TestFunction(double x, double y,
    out double x_times_y, out double x_dividedBy_y)
{
    x_times_y = x * y;
    if (y != 0)
    { x_dividedBy_y = x / y; }
    else
    { x_dividedBy_y = double.NaN; }
    return true;
}

Python では、この関数を次のように呼び出す必要があります。

success, x_times_y, x_dividedBy_y = reader.TestFunction(1.2, 2.3, 0, 0)

最後の 2 つの引数 (2 つのゼロ) は、ダミーの変数にすぎません。

多くの場合、Python for .NET では出力引数の型が自動的に検出されるので、Python の変数は単純に API のメソッドに渡すだけで済みます。それでも、Python for .NET では型を検出できない出力引数を持つメソッドもあります。特に、同じ名前空間で同じ名前を持ち、所属先のインターフェイスのみが異なるメソッドで、このような状況が発生します。このようなメソッドには複数の出力変数が存在することがあります。その場合は、各変数を明示的に .NET 変数として定義し、渡す必要があります。明示的に .NET 変数を呼び出すには、次のように、その型の変数をインポートし、その値をデフォルト値に初期化します。

from System import Int32, Double
# Python NET requires all arguments to be passed in as reference, so need to have placeholders
sysInt = Int32(1)
sysDbl = Double(1.0)

{Zemax}\ZOS-API Sample Code\Python ディレクトリにある例 22 と例 23 では、ReadNextResult() メソッドの使用例を紹介しています。

 

C# の外部 DLL の使用

Python.net では、大規模なデータ セットのバッチ処理に外部 DLL を使用できます。たとえば、シーケンシャル光線追跡の実行や ZRD ファイルの解析で、Python.net から "RayTrace.dll" を直接使用できます。"RayTrace.dll" の詳細については、記事「MATLAB で ZOS-API を使用して光線追跡データをバッチ処理する方法」を参照してください。

Python.NET との相互処理を目的として C# の外部 DLL を新規作成する場合は、以下の点を確認します。

  1. C# のクラスをパブリックとして宣言する必要があります。宣言していないクラスはデフォルトで内部クラスとされ、PythonNET CLR にアクセスできないからです。
  2. ソリューション エクスプローラでプロジェクトのプロパティにアクセスし、[アプリケーション] (Application) → [出力の種類] (Output Type) を [クラス ライブラリ] (Class Library) に設定していることを確認します。
  3. デバッグ モードまたはリリース モードで DLL をコンパイルします (他のユーザーに DLL を配布する場合は、必ずリリース モードを選択します)。

DLL の読み込み

C# の DLL を読み込むには、次のように、clr.AddReference メソッドを使用して、DLL のファイルが置かれている場所を渡します。

# load the DLL
clr.AddReference(os.path.abspath(os.path.join(os.path.abspath(''), '..', r'ZBFReader\bin\Release\ZOSReader.dll')));
Then you can import the namespace(s) from the DLL:
# import the namespace
import Reader

クラスの呼び出し

クラスを呼び出すには、namespace.class_init 関数を使用します。

# load the class
reader = Reader.ZBF(os.path.join(ZemaxDataDir, r'POP\BEAMFILES\F.ZBF'))

 

double[] から numpy への変換

配列をループ処理するうえで Python は非効率的です。map や filter などの組込みコマンドを使用し、numpy などのコンパイル済みライブラリに配列を渡す方がはるかに効率的です。

  • インデックスが少ない配列では、それらの値を直接読み取ることができます。
    しかし、インデックスの数が数百から数千に及ぶ配列では、numpy を使用して反復処理でデータを操作することをお勧めします。
  • C# の DLL から返される配列はすべて、int[] または double[] の形式になるので、それを numpy で認識できる形式に変換する必要があります。
    すべてのインデックスを反復処理して numpy の配列に渡すのは非効率的なので、新しい関数 (def) を使用します。この関数は、int[] または double[] をそのメモリ アドレスから読み取り、その値をメモリから numpy に直接渡します。

def DoubleToNumpy(data):
    if 'numpy' not in sys.modules:
        print('You have not imported numpy into this file')
        return False
    else:
        src_hndl = GCHandle.Alloc(data, GCHandleType.Pinned)
        try:
            src_ptr = src_hndl.AddrOfPinnedObject().ToInt64()
            cbuf = (ctypes.c_double*len(data)).from_address(src_ptr)
            npData = np.frombuffer(cbuf, dtype=np.float64)
        finally:
            if src_hndl.IsAllocated: src_hndl.Free()
        return npData

 

KA-01951

この記事は役に立ちましたか?
13人中5人がこの記事が役に立ったと言っています

コメント

0件のコメント

記事コメントは受け付けていません。