6

私の会社では、エレクトロミオグラフィ データにレガシー ファイル形式を使用していますが、これは現在は生産されていません。ただし、レトロ互換性を維持することに関心があるため、そのファイル形式用のリーダーを作成する可能性を検討しています。

Delphi で書かれた非常に複雑な以前のソース コードを分析することにより、ファイル リーダー/ライターは ZLIB を使用し、HexEditor 内にはバイナリ ASCII のファイル ヘッダーがあるように見えます ("Player"、"Analyzer" などのフィールドは容易に読み取れます)。生データを含む圧縮された文字列が続きます。

私の疑問は、次のことを特定するためにどのように進めるべきかです。

  • 圧縮ストリームの場合。
  • 圧縮ストリームの開始位置と終了位置。

ウィキペディアから:

zlib 圧縮データは通常、gzip または zlib ラッパーで書き込まれます。ラッパーは、ヘッダーとトレーラーを追加することにより、未加工の DEFLATE データをカプセル化します。これにより、ストリームの識別とエラー検出が提供されます

これは関連していますか?

より多くの情報を投稿できることを嬉しく思いますが、何が最も関連性があるかわかりません。

ヒントをありがとう。

編集:私は動作中のアプリケーションを持っており、それを使用して任意の時間の長さの実際のデータを記録し、必要に応じて 1kB よりも小さいファイルを取得できます。


いくつかのサンプル ファイル:

データストリームなしで新しく作成されたもの: https://dl.dropbox.com/u/4849855/Mio_File/HeltonEmpty.mio

非常に短い (1 秒?) データストリームが保存された後の上記と同じ: https://dl.dropbox.com/u/4849855/Mio_File/HeltonFilled.mio

「Helton」ではなく「manco」という名前の患者からの別のもので、さらに短いストリーム (16 進数表示に最適): https://dl.dropbox.com/u/4849855/Mio_File/manco_short.mio

指示: 各ファイルは、患者 (個人) のファイルである必要があります。これらのファイル内には、1 つ以上の検査が保存され、各検査は 1 つ以上の時系列で構成されます。提供されたファイルには、1 つのデータ系列を持つ 1 つの検査のみが含まれています。

4

2 に答える 2

10

まず、すべての有効な zip ストリームのファイルをスキャンしてみませんか (小さなファイルの場合や形式を把握するには十分です)。

import zlib
from glob import glob

def zipstreams(filename):
    """Return all zip streams and their positions in file."""
    with open(filename, 'rb') as fh:
        data = fh.read()
    i = 0
    while i < len(data):
        try:
            zo = zlib.decompressobj()
            yield i, zo.decompress(data[i:])
            i += len(data[i:]) - len(zo.unused_data)
        except zlib.error:
            i += 1

for filename in glob('*.mio'):
    print(filename)
    for i, data in zipstreams(filename):
        print (i, len(data))

データ ストリームにはリトルエンディアンの倍精度浮動小数点データが含まれているようです。

import numpy
from matplotlib import pyplot

for filename in glob('*.mio'):
    for i, data in zipstreams(filename):
        if data:
            a = numpy.fromstring(data, '<f8')
            pyplot.plot(a[1:])
            pyplot.title(filename + ' - %i' % i)
            pyplot.show()
于 2012-08-28T00:35:03.267 に答える
8

zlib は、 DEFLATEアルゴリズムで圧縮されたデータの薄いラッパーであり、 RFC1950で定義されています。

  A zlib stream has the following structure:

       0   1
     +---+---+
     |CMF|FLG|   (more-->)
     +---+---+

  (if FLG.FDICT set)

       0   1   2   3
     +---+---+---+---+
     |     DICTID    |   (more-->)
     +---+---+---+---+

     +=====================+---+---+---+---+
     |...compressed data...|    ADLER32    |
     +=====================+---+---+---+---+

そのため、未加工の DEFLATE 圧縮データの前に少なくとも 2 バイト、場合によっては 6 バイトを追加し、 ADLER32チェックサムを使用して 4 バイトを追加します。

最初のバイトにはCMF (圧縮方法とフラグ) が含まれ、 CM (圧縮方法) (最初の 4 ビット) とCINFO (圧縮情報) (最後の 4 ビット)に分割されます。

このことから、残念ながらすでに zlib ストリームの最初の 2 バイトが、使用されている圧縮方法と設定によって大きく異なる可能性があることは明らかです。

幸いなことに、私は ADLER32 アルゴリズムの作成者である Mark Adler による投稿を見つけました。そこでは、これら 2 つの開始バイトの最も一般的な組み合わせと一般的ではない組み合わせがリストされています

それはさておき、Python を使用して zlib を調べる方法を見てみましょう。

>>> import zlib
>>> msg = 'foo'
>>> [hex(ord(b)) for b in zlib.compress(msg)]
['0x78', '0x9c', '0x4b', '0xcb', '0xcf', '0x7', '0x0', '0x2', '0x82', '0x1', '0x45']

zlibそのため、Python のモジュールによって (デフォルト オプションを使用して) 作成された zlib データは78 9c. これを使用して、プリアンブル、いくつかの zlib 圧縮データ、およびフッターを含むカスタム ファイル形式を書き込むスクリプトを作成します。

次に、その 2 バイト パターンのファイルをスキャンし、zlib ストリームとして続くすべての圧縮解除を開始し、ストリームの終了位置とフッターの開始位置を特定する 2 つ目のスクリプトを作成します。

create.py

import zlib

msg = 'foo'
filename = 'foo.compressed'

compressed_msg = zlib.compress(msg)
data = 'HEADER' + compressed_msg + 'FOOTER'

with open(filename, 'wb') as outfile:
    outfile.write(data)

ここではmsg、ファイルに書き出す前に、zlib で圧縮し、ヘッダーとフッターで囲みます。

この例では、ヘッダーとフッターは固定長ですが、もちろん任意の不明な長さにすることもできます。

次に、そのようなファイルで zlib ストリームを見つけようとするスクリプトについて説明します。この例では、予想されるマーカーが正確にわかっているため、使用しているのは 1 つだけですが、ZLIB_MARKERS上記の投稿のすべてのマーカーでリストを埋めることができることは明らかです。

ident.py

import zlib

ZLIB_MARKERS = ['\x78\x9c']
filename = 'foo.compressed'

infile = open(filename, 'r')
data = infile.read()

pos = 0
found = False

while not found:
    window = data[pos:pos+2]
    for marker in ZLIB_MARKERS:
        if window == marker:
            found = True
            start = pos
            print "Start of zlib stream found at byte %s" % pos
            break
    if pos == len(data):
        break
    pos += 1

if found:
    header = data[:start]

    rest_of_data = data[start:]
    decomp_obj = zlib.decompressobj()
    uncompressed_msg = decomp_obj.decompress(rest_of_data)

    footer = decomp_obj.unused_data

    print "Header: %s" % header
    print "Message: %s" % uncompressed_msg
    print "Footer: %s" % footer

if not found:
    print "Sorry, no zlib streams starting with any of the markers found."

アイデアは次のとおりです。

  • ファイルの先頭から始めて、2 バイトの検索ウィンドウを作成します。

  • 検索ウィンドウを 1 バイト単位で前方に移動します。

  • すべてのウィンドウについて、定義した 2 バイト マーカーのいずれかと一致するかどうかを確認します。

  • 一致が見つかった場合は、開始位置を記録し、検索を停止して、後続のすべてを解凍してみてください。

ここで、ストリームの終わりを見つけることは、2 つのマーカー バイトを探すことほど簡単ではありません。zlib ストリームは、固定バイト シーケンスで終了することも、その長さがヘッダー フィールドに示されることもありません。代わりに、この時点までのデータと一致する必要がある 4 バイトの ADLER32 チェックサムで終了します。

そのしくみは、内部の C 関数がinflate()、ストリームを読み取るときに継続的に圧縮解除を試行し続け、一致するチェックサムに遭遇した場合、そのことを呼び出し元に通知し、残りのデータがストリームの一部ではないことを示します。 zlib ストリームはもうありません。

Python では、単純に を呼び出すのではなく、解凍オブジェクトを使用すると、この動作が明らかになりますzlib.decompress()。オブジェクトを呼び出すdecompress(string)と、zlib ストリームが解凍され、ストリームの一部であった解凍されたデータが返されます。ストリームに続くものはすべて に保存され、後で取得できます。Decompressstringunused_data

これにより、最初のスクリプトで作成されたファイルに次の出力が生成されます。

Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER

この例は、圧縮されていないメッセージを印刷する代わりにファイルに書き込むように簡単に変更できます。次に、以前に zlib で圧縮されたデータをさらに分析し、分離したヘッダーとフッターのメタデータで既知のフィールドを特定しようとします。

于 2012-08-27T22:45:59.463 に答える