├── distribution.bat
├── data
├── beam.cihx
├── beam.mraw
├── ball_12bit.cihx
├── ball_12bit.mraw
├── sample_60k_16bit.mraw
└── sample_60k_16bit.cih
├── requirements.dev.txt
├── requirements.txt
├── LICENSE
├── .github
└── workflows
│ ├── python-package.yml
│ └── release-and-publish-to-pypi.yml
├── setup.py
├── pyproject.toml
├── readme.rst
├── .gitignore
├── sync_version.py
├── tests
└── test_all.py
├── pyMRAW.py
└── Showcase.ipynb
/distribution.bat:
--------------------------------------------------------------------------------
1 | python setup.py sdist bdist_wheel
2 | twine upload dist/*
--------------------------------------------------------------------------------
/data/beam.cihx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ladisk/pyMRAW/HEAD/data/beam.cihx
--------------------------------------------------------------------------------
/data/beam.mraw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ladisk/pyMRAW/HEAD/data/beam.mraw
--------------------------------------------------------------------------------
/data/ball_12bit.cihx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ladisk/pyMRAW/HEAD/data/ball_12bit.cihx
--------------------------------------------------------------------------------
/data/ball_12bit.mraw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ladisk/pyMRAW/HEAD/data/ball_12bit.mraw
--------------------------------------------------------------------------------
/data/sample_60k_16bit.mraw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ladisk/pyMRAW/HEAD/data/sample_60k_16bit.mraw
--------------------------------------------------------------------------------
/requirements.dev.txt:
--------------------------------------------------------------------------------
1 | -r requirements.txt
2 | sphinx
3 | twine
4 | wheel
5 | build
6 | pytest
7 | sphinx-rtd-theme
8 | sphinx-copybutton>=0.5.2
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | colorama>=0.3.7
2 | nose>=1.3.7
3 | numpy>=1.12.0
4 | py>=1.4.32
5 | pytest>=3.0.5
6 | xmltodict>=0.12.0
7 | numba>=0.56.4
8 | build
9 | twine
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 LADISK
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | python-version: ["3.10", "3.11", "3.12"]
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v4
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install flake8 pytest
23 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
24 | - name: Lint with flake8
25 | run: |
26 | # stop the build if there are Python syntax errors or undefined names
27 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
28 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
29 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
30 | - name: Test with pytest
31 | run: |
32 | pytest
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | desc = """\
2 | pyMRAW
3 | ======
4 |
5 | Module for reading Photron MRAW image sequences.
6 | -----------------------------------------------------------
7 | We developed this module while working on this publication:
8 |
9 | J. Javh, J. Slavič and M. Boltežar: The Subpixel Resolution of Optical-Flow-Based Modal Analysis,
10 | Mechanical Systems and Signal Processing, Vol. 88, p. 89–99, 2017
11 |
12 | Our recent research effort can be found here: http://lab.fs.uni-lj.si/ladisk/?what=incfl&flnm=research_filtered.php&keyword=optical%20methods
13 |
14 | If you find our research useful, consider to cite us.
15 |
16 | """
17 |
18 | #from distutils.core import setup, Extension
19 | from setuptools import setup, Extension
20 | from pyMRAW import __version__
21 | setup(name='pyMRAW',
22 | version=__version__,
23 | author='Jaka Javh, Janko Slavič, Domen Gorjup',
24 | author_email='jaka.javh@fs.uni-lj.si,janko.slavic@fs.uni-lj.si, domen.gorjup@fs.uni-lj.si',
25 | description='Module for reading and writing Photron MRAW image sequences.',
26 | url='https://github.com/ladisk/pyMRAW',
27 | py_modules=['pyMRAW'],
28 | #ext_modules=[Extension('lvm_read', ['data/short.lvm'])],
29 | long_description=desc,
30 | install_requires=['numpy>=1.10.0', 'xmltodict>=0.12.0', 'numba>=0.56.4']
31 | )
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "hatchling",
4 | ]
5 | build-backend = "hatchling.build"
6 |
7 | [project]
8 | name = "pyMRAW"
9 | version = "0.33.0"
10 | authors = [
11 | { name = "Jaka Javh, Janko Slavič, Domen Gorjup", email = "janko.slavic@fs.uni-lj.si" },
12 | ]
13 | maintainers = [
14 | { name = "Janko Slavič et al.", email = "janko.slavic@fs.uni-lj.si" },
15 | ]
16 | license = "MIT"
17 | description = "Module for reading and writing Photron MRAW image sequences."
18 | readme = "readme.rst"
19 | keywords = [
20 | "read/write",
21 | "Photron",
22 | "mraw",
23 | "cihx",
24 | "cih",
25 | ]
26 | requires-python = ">=3.10"
27 | dependencies = [
28 | "colorama>=0.3.7",
29 | "nose>=1.3.7",
30 | "numpy>=1.12.0",
31 | "py>=1.4.32",
32 | "xmltodict>=0.12.0",
33 | "numba>=0.56.4",
34 | ]
35 | classifiers = [
36 | "Development Status :: 5 - Production/Stable",
37 | "Intended Audience :: Developers",
38 | "Topic :: Scientific/Engineering",
39 | "Programming Language :: Python :: 3.10",
40 | "License :: OSI Approved :: MIT License",
41 | ]
42 |
43 | [project.optional-dependencies]
44 | dev = [
45 | "sphinx",
46 | "twine",
47 | "wheel",
48 | "build",
49 | "pytest",
50 | "sphinx-rtd-theme",
51 | "sphinx-copybutton>=0.5.2",
52 | ]
53 |
54 | [project.urls]
55 | homepage = "https://github.com/ladisk/pyMRAW"
56 | documentation = "https://github.com/ladisk/pyMRAW"
57 | source = "https://github.com/ladisk/pyMRAW"
58 |
--------------------------------------------------------------------------------
/readme.rst:
--------------------------------------------------------------------------------
1 | pyMRAW
2 | ======
3 |
4 | Photron MRAW File Reader.
5 | -------------------------
6 |
7 | `pyMRAW` is an open-source package, enabling the efficient use of the Photron MRAW video files in Python workflows.
8 |
9 | It's main feature is the use of memory-mapped (`np.memmap`) arrays to create memory maps to locally stored raw video files and avoid loading large amounts of data into RAM.
10 |
11 | .. warning::
12 | To take advantage of pyMRAW's memory-mapping functionality, make sure to save MRAW files either in 8-bit or 16-bit formats, corresponding to standard data types `uint8` and `uint16`! Using pyMRAW to read 12-bit MRAW files is possible, but requires loading the complete image data into RAM to produce standard Numpy arrays.
13 |
14 | To load `.mraw` - `.cihx` files, simply use the `pymraw.load_video` function::
15 |
16 | import pyMRAW
17 | images, info = pyMRAW.load_video('data/beam.cihx')
18 |
19 | For more info, please refer to the `Showcase.ipynb` notebook.
20 |
21 | We developed this module while working on this publication:
22 | J. Javh, J. Slavič and M. Boltežar: The Subpixel Resolution of Optical-Flow-Based Modal Analysis,
23 | Mechanical Systems and Signal Processing, Vol. 88, p. 89–99, 2017
24 |
25 | Our recent research effort can be found here: http://lab.fs.uni-lj.si/ladisk/?what=incfl&flnm=research_filtered.php&keyword=optical%20methods
26 |
27 | If you find our research useful, consider to cite us.
28 |
29 |
30 | |pytest|
31 |
32 | .. |pytest| image:: https://github.com/ladisk/pyMRAW/actions/workflows/python-package.yml/badge.svg
33 | :target: https://github.com/ladisk/pyMRAW/actions
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | # Created by .ignore support plugin (hsz.mobi)
3 | ### Python template
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | env/
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # SageMath parsed files
83 | *.sage.py
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # other
100 | server_user_id.txt
101 | temp/
102 |
--------------------------------------------------------------------------------
/.github/workflows/release-and-publish-to-pypi.yml:
--------------------------------------------------------------------------------
1 | name: Release and Publish to PyPI
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | permissions:
9 | contents: write
10 | packages: write
11 |
12 | jobs:
13 | release:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - uses: actions/setup-python@v5
20 | with:
21 | python-version: 3.11
22 |
23 | - name: Install dependencies
24 | run: pip install build tomli tomli-w
25 |
26 | # Optional (run a python script to sync versions with the tag)
27 | - name: Extract tag version
28 | id: tag
29 | run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
30 |
31 | - name: Set version from tag
32 | run: python sync_version.py --set-version ${{ steps.tag.outputs.version }}
33 |
34 | - name: Check if there are changes
35 | id: diff
36 | run: |
37 | if git diff --quiet; then
38 | echo "changed=false" >> $GITHUB_OUTPUT
39 | else
40 | echo "changed=true" >> $GITHUB_OUTPUT
41 | fi
42 |
43 | - name: Commit version sync (if needed)
44 | if: steps.diff.outputs.changed == 'true'
45 | run: |
46 | git config user.name "github-actions"
47 | git config user.email "github-actions@github.com"
48 | git commit -am "sync version to ${{ steps.tag.outputs.version }}"
49 | git push origin HEAD:master
50 | # ---
51 |
52 | - name: Build distribution
53 | run: python -m build
54 |
55 | - name: Create GitHub Release and upload artifacts
56 | uses: softprops/action-gh-release@v2
57 | with:
58 | files: dist/*.whl
59 |
60 | - name: Publish to PyPI
61 | uses: pypa/gh-action-pypi-publish@v1.13.0
62 | with:
63 | user: __token__
64 | password: ${{ secrets.PYPI_API_TOKEN }}
--------------------------------------------------------------------------------
/sync_version.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import tomli
4 | import tomli_w
5 | import argparse
6 |
7 | package_name = "pyMRAW"
8 |
9 | def synchronize_version():
10 | print("Synchronizing version (pyproject.toml and __init__.py)...")
11 |
12 | # Read the version from pyproject.toml
13 | with open("pyproject.toml", "rb") as f:
14 | pyproject = tomli.load(f)
15 |
16 | version_toml = pyproject["project"]["version"]
17 |
18 | # Read the __init__.py
19 | with open(f"pyMRAW.py", "r") as f:
20 | init = f.readlines()
21 |
22 | # Replace the version with the one from pyproject.toml
23 | for i, line in enumerate(init):
24 | if "__version__" in line:
25 | init[i] = "__version__ = " + f'"{version_toml}"' + "\n"
26 | init = "".join(init)
27 |
28 | # Write the new __init__.py
29 | with open(f"pyMRAW.py", "w") as f:
30 | f.write(init)
31 |
32 | # Update docs/source/conf.py
33 | # with open("docs/source/conf.py", "r", encoding="utf8") as f:
34 | # conf = f.readlines()
35 |
36 | # for i, line in enumerate(conf):
37 | # if "version = " in line and not line.strip().startswith("#"):
38 | # conf[i] = f"version = '{version_toml.rsplit('.', 1)[0]}'\n"
39 | # elif "release = " in line and not line.strip().startswith("#"):
40 | # conf[i] = f"release = '{version_toml}'\n"
41 |
42 | # # Write the new conf.py
43 | # with open("docs/source/conf.py", "w", encoding="utf8") as f:
44 | # f.write("".join(conf))
45 |
46 | def set_version(version):
47 | with open("pyproject.toml", "rb") as f:
48 | pyproject = tomli.load(f)
49 | pyproject["project"]["version"] = version
50 | with open("pyproject.toml", "wb") as f:
51 | tomli_w.dump(pyproject, f)
52 |
53 | def bump_version(bump):
54 | with open("pyproject.toml", "rb") as f:
55 | pyproject = tomli.load(f)
56 | version = pyproject["project"]["version"]
57 | version_parts = version.split(".")
58 | if bump == "patch":
59 | version_parts[2] = str(int(version_parts[2]) + 1)
60 | elif bump == "minor":
61 | version_parts[1] = str(int(version_parts[1]) + 1)
62 | version_parts[2] = "0"
63 | elif bump == "major":
64 | version_parts[0] = str(int(version_parts[0]) + 1)
65 | version_parts[1] = "0"
66 | version_parts[2] = "0"
67 | else:
68 | raise ValueError(f"Invalid bump type: {bump}")
69 |
70 | version = ".".join(version_parts)
71 | set_version(version)
72 |
73 | if __name__ == "__main__":
74 | parser = argparse.ArgumentParser()
75 | parser.add_argument("--bump", default="", choices=["patch", "minor", "major"], help="Bump the version of the package.")
76 | parser.add_argument("--set-version", type=str, help="Set the version of the package.")
77 | args = parser.parse_args()
78 |
79 | if args.set_version:
80 | set_version(args.set_version)
81 |
82 | elif args.bump:
83 | bump_version(args.bump)
84 |
85 | synchronize_version()
--------------------------------------------------------------------------------
/tests/test_all.py:
--------------------------------------------------------------------------------
1 | """
2 | Unit test for pyMRAW.py
3 | """
4 | import pytest
5 | import numpy as np
6 | import sys, os
7 | import tempfile
8 |
9 | myPath = os.path.dirname(os.path.abspath(__file__))
10 | sys.path.insert(0, myPath + '/../')
11 |
12 | import pyMRAW
13 |
14 | @pytest.mark.filterwarnings('ignore')
15 | def test():
16 | filename = './data/sample_60k_16bit.cih'
17 | cih = pyMRAW.get_cih(filename)
18 | N = cih['Total Frame']
19 | h = cih['Image Height']
20 | w = cih['Image Width']
21 |
22 | np.testing.assert_equal(N, 12)
23 | np.testing.assert_equal(h, 368)
24 | np.testing.assert_equal(w, 896)
25 | #if N > 12:
26 | # N = 12
27 | mraw = open(filename[:-4] + '.mraw', 'rb')
28 | mraw.seek(0, 0) # find the beginning of the file
29 | image_data = pyMRAW.load_images(mraw, h, w, N) # load N images
30 | #np.memmap in load_images loads enables reading an array from disc as if from RAM. If you want all the images to load on RAM imediatly use load_images(mraw, h, w, N).copy()
31 | mraw.close()
32 | np.testing.assert_allclose(image_data[0,0,0],1889, atol=1e-8)
33 |
34 |
35 | @pytest.mark.filterwarnings('ignore')
36 | def test_cihx():
37 | filename = './data/beam.cihx'
38 | cih = pyMRAW.get_cih(filename)
39 | N = cih['Total Frame']
40 | h = cih['Image Height']
41 | w = cih['Image Width']
42 | mraw = pyMRAW.load_images(filename[:-5] + '.mraw', h, w, N, bit=16, roll_axis=False)
43 | np.testing.assert_equal(mraw.shape, (4, 80, 1024))
44 |
45 | @pytest.mark.filterwarnings('ignore')
46 | def test_12bit_cihx():
47 | filename = './data/ball_12bit.cihx'
48 | cih = pyMRAW.get_cih(filename)
49 | N = cih['Total Frame']
50 | h = cih['Image Height']
51 | w = cih['Image Width']
52 | mraw = pyMRAW.load_images(filename[:-5] + '.mraw', h, w, N, bit=12, roll_axis=False)
53 | np.testing.assert_equal(mraw.shape, (15, 384, 384))
54 | np.testing.assert_equal(mraw.dtype, np.dtype(np.uint16))
55 |
56 |
57 | @pytest.mark.filterwarnings('ignore')
58 | def test_save_mraw():
59 | root_dir = './tests'
60 | with tempfile.TemporaryDirectory(dir=root_dir) as tmpdir:
61 |
62 | images8 = np.ones((3, 4, 5), dtype=np.uint8)
63 | images16 = np.ones((2, 3, 4), dtype=np.uint16) * (2**12-1)
64 |
65 | mraw8, cih8 = pyMRAW.save_mraw(images8, os.path.join(tmpdir, 'test8.cih'), bit_depth=8, ext='mraw', info_dict={'Record Rate(fps)':10})
66 | mraw8_16, cih8_16 = pyMRAW.save_mraw(images8, os.path.join(tmpdir, 'test8_16.cih'), bit_depth=16, ext='mraw', info_dict={'Shutter Speed(s)':0.001})
67 | mraw16, cih16 = pyMRAW.save_mraw(images16, os.path.join(tmpdir, 'test16.cih'), bit_depth=16, ext='mraw', info_dict={'Comment Text':'Test saving 16 bit images.'})
68 |
69 | loaded_images8, info8 = pyMRAW.load_video(cih8)
70 | loaded_images8_16, info8_16 = pyMRAW.load_video(cih8_16)
71 | loaded_images16, info16 = pyMRAW.load_video(cih16)
72 |
73 | assert loaded_images8.shape == images8.shape
74 | assert loaded_images8_16.shape == images8.shape
75 | assert loaded_images16.shape == images16.shape
76 | assert loaded_images8.dtype == np.uint8
77 | assert loaded_images8_16.dtype == np.uint16
78 | assert loaded_images16.dtype == np.uint16
79 | assert info8['Record Rate(fps)'] == 10
80 | assert info8_16['Shutter Speed(s)'] == 0.001
81 | assert info16['Comment Text'] == 'Test saving 16 bit images.'
82 |
83 | loaded_images8._mmap.close()
84 | loaded_images8_16._mmap.close()
85 | loaded_images16._mmap.close()
86 |
87 |
88 | if __name__ == '__main__':
89 | np.testing.run_module_suite()
--------------------------------------------------------------------------------
/data/sample_60k_16bit.cih:
--------------------------------------------------------------------------------
1 | #Camera Information Header
2 | Date : 2016/4/21
3 | Time : 00:11
4 | Camera Type : FASTCAM SA-Z type 2100K-M-64GB
5 | Head Type : Unknown Child Device
6 | Camera ID : 10
7 | Camera Number : 0
8 | Head Number : 1
9 | Max Head Number : 1
10 | Scene Name :
11 | User Defined Camera Name :
12 | Session Number :
13 | Date Record : Unknown
14 | Time Record : Unknown
15 | Trigger Time : 0
16 | Record Rate(fps) : 60000
17 | Shutter Speed(s) : 1/800000
18 | Trigger Mode : Start
19 | Original Total Frame : 677
20 | Total Frame : 12
21 | Start Frame : 49300
22 | Correct Trigger Frame : 1
23 | Save Step : 36
24 | Image Width : 896
25 | Image Height : 368
26 | Color Type : Mono
27 | Color Bit : 16
28 | File Format : MRaw
29 | EffectiveBit Depth : 12
30 | EffectiveBit Side : Lower
31 | Partition Number: 1
32 | Digits Of File Number : 6
33 | Device Last Error : -1:0xffffffff
34 | Comment Text : first test
35 | AnalogBoard Channel Num : 0
36 | Zero Frame : Exist
37 | Adjust Trigger Point : On
38 | Shutter Type2(nsec) : 1250
39 | Edge Enhance : 0
40 | Pre LUT Mode : DEF1
41 | Pre LUT Brightness : 0
42 | Pre LUT Contrast : 0
43 | Pre LUT Gain : 1.0
44 | Pre LUT Gamma : 1.00
45 | Pre LUT PosiNega : Posi
46 | DS Shutter : Value 0
47 | Scale Method : 1
48 | Scale Grid Space : 10
49 | Scale Pixel Size : 8.2148
50 | Scale Unit : 1
51 | Scale Magnification : 1.0000
52 | Scale Ruler : 10
53 | Scale Distance : 600.0000
54 | Scale 2Points Distance : 73.0392
55 | Polarization : 0
56 | Pola Config : 0
57 | Pola Save Kind : 0
58 | Pola Save Comment :
59 | Time Code Type : 0
60 | Time Code Display Unit :
61 | Time Code Per Frames : 0.000000
62 | Time Code Unit Reset : 0.000000
63 | Time Code Reset : 0
64 | Current Time Decimal Place : 0
65 | Current Time Max Display Unit : 0
66 | Current Time Decimal Divide : 0
67 | Not Use :
68 | Not Use :
69 | Not Use :
70 | Auto Exposure : Off
71 | Shading : On
72 | Pixel Gain : Default
73 | Sync In : OFF
74 | General In : EVENT POS
75 | Trig TTL In : TRIG POS
76 | General Out1 : SYNC POS
77 | General Out2 : SYNC POS
78 | General Out3 : SYNC POS
79 | Trig TTL Out : TRIG POS
80 | SYNC OUT Times(times) : 1
81 | IRIG : Off
82 | IRIG Phase Lock : Off
83 | Video Out : PAL
84 | Video Out during Memory : Off
85 | Total Partition : 1
86 | Current Partition : 1
87 | Partition 1 : 138927(frame)
88 | Hardware Partition Increment Mode : Off
89 | Signal Delay Trigger In(nsec) : 0
90 | Signal Delay Sync In(nsec) : 0
91 | Signal Delay General In(nsec) : 0
92 | Signal Delay VSync Out(nsec) : 0
93 | Signal Delay Exposure Out(nsec) : 0
94 | Signal Delay Trigger Out Width(nsec) : 10000
95 | Signal Delay VSync Out Width(nsec) : 10000
96 | Locked Shutter Speed : Off
97 | Hardware Recording Type : READY AND TRIG
98 | Locked Resolution : Off
99 |
100 | #Photron Fastcam Viewer Version Information (Resave):
101 | Saved Date : 2016/6/13
102 | Saved Time : 15:06
103 | PFV.exe : 3.6.3.0
104 | 1024PCIInst.exe : 1.1.0.0
105 | 512PCIInst.exe : 1.1.0.0
106 | node.exe : 0.10.26
107 | D1024PCI.dll : 2.0.0.0
108 | D512PCI.dll : 2.0.0.0
109 | DAPX.dll : 2.0.0.0
110 | DAPXRS.dll : 2.0.0.0
111 | DAX.dll : 2.2.0.0
112 | DAX100.dll : 2.2.0.0
113 | DAX50.dll : 2.2.0.0
114 | DBC2.dll : 2.0.0.0
115 | DIDPEX.dll : 2.0.0.1
116 | DMC1.dll : 2.0.0.1
117 | DMH4.dll : 2.0.0.0
118 | DSA1.dll : 2.0.0.0
119 | DSA2.dll : 2.0.0.0
120 | DSA3.dll : 2.0.0.0
121 | DSA4.dll : 2.0.0.0
122 | DSA5.dll : 2.0.0.0
123 | DSA6.dll : 2.0.0.0
124 | DSA7.dll : 2.0.0.0
125 | DSA8.dll : 2.0.0.0
126 | DSAX.dll : 2.1.0.0
127 | DSAX_B.dll : 2.2.0.0
128 | DSAZ.dll : 2.2.0.0
129 | DTX.dll : 2.0.0.2
130 | DUL512.dll : 2.0.0.0
131 | DUX.dll : 2.1.0.0
132 | DUX50.dll : 2.1.0.0
133 | DWX.dll : 2.1.0.0
134 | DWX50.dll : 2.1.0.0
135 | FHSDLCTRL.dll : 1.1.0.0
136 | FSDCTRL.dll : 2.1.0.0
137 | GEthLib.dll : 2.0.0.1
138 | I1394.dll : 2.0.0.0
139 | IGETHER.dll : 2.0.0.1
140 | ijl20.dll : 2,0,18,50
141 | IOPTICAL.dll : 2.0.0.0
142 | IPCI.dll : 2.0.0.0
143 | ippcce9-6.1.dll : 6,1,137,734
144 | ippccem64t-6.1.dll : 6,1,137,734
145 | ippccm7-6.1.dll : 6,1,137,734
146 | ippccmx-6.1.dll : 6,1,137,734
147 | ippccn8-6.1.dll : 6,1,137,734
148 | ippccu8-6.1.dll : 6,1,137,734
149 | ippccy8-6.1.dll : 6,1,137,734
150 | ippcoreem64t-6.1.dll : 6,1,137,790
151 | ippcve9-6.1.dll : 6,1,137,811
152 | ippcvem64t-6.1.dll : 6,1,137,811
153 | ippcvm7-6.1.dll : 6,1,137,811
154 | ippcvmx-6.1.dll : 6,1,137,811
155 | ippcvn8-6.1.dll : 6,1,137,811
156 | ippcvu8-6.1.dll : 6,1,137,811
157 | ippcvy8-6.1.dll : 6,1,137,811
158 | ippie9-6.1.dll : 6,1,137,825
159 | ippiem64t-6.1.dll : 6,1,137,825
160 | ippim7-6.1.dll : 6,1,137,825
161 | ippimx-6.1.dll : 6,1,137,825
162 | ippin8-6.1.dll : 6,1,137,825
163 | ippiu8-6.1.dll : 6,1,137,825
164 | ippiy8-6.1.dll : 6,1,137,825
165 | ippje9-6.1.dll : 6,1,137,803
166 | ippjem64t-6.1.dll : 6,1,137,803
167 | ippjm7-6.1.dll : 6,1,137,803
168 | ippjmx-6.1.dll : 6,1,137,803
169 | ippjn8-6.1.dll : 6,1,137,803
170 | ippju8-6.1.dll : 6,1,137,803
171 | ippjy8-6.1.dll : 6,1,137,803
172 | ippsce9-6.1.dll : 6,1,137,798
173 | ippscem64t-6.1.dll : 6,1,137,798
174 | ippscm7-6.1.dll : 6,1,137,798
175 | ippscmx-6.1.dll : 6,1,137,798
176 | ippscn8-6.1.dll : 6,1,137,798
177 | ippscu8-6.1.dll : 6,1,137,798
178 | ippscy8-6.1.dll : 6,1,137,798
179 | ippse9-6.1.dll : 6,1,137,827
180 | ippsem64t-6.1.dll : 6,1,137,827
181 | ippsm7-6.1.dll : 6,1,137,827
182 | ippsmx-6.1.dll : 6,1,137,827
183 | ippsn8-6.1.dll : 6,1,137,827
184 | libguide40.dll : 20071022
185 | libiomp5md.dll : 20071022
186 | libpng12.dll : ???
187 | libpng13.dll : 1.2.12
188 | PDCLIB.dll : 2.2.0.0
189 | PDCPOLA.dll : 1, 0, 1, 0
190 | PICLIB.dll : 2.1.0.0
191 | PluginCamera.dll : 1.6.0.3
192 | PluginDataSave.dll : 1.6.0.3
193 | PluginFileView.dll : 1.6.0.3
194 | zlib.dll : 1.2.7
195 | zlib1.dll : 1.2.3
196 | PluginAngle.pdll : 2.0.0.0
197 | PluginBatchDataConverter.pdll : 2.0.0.0
198 | PluginComm.pdll : 2.0.0.0
199 | PluginCycleView.pdll : 2.0.1.0
200 | PluginDistance.pdll : 2.0.0.0
201 | PluginHDR.pdll : 2.0.0.0
202 | PluginHistogram.pdll : 2.0.0.0
203 | PluginLEDLaserControl.pdll : 2.0.0.0
204 | PluginLensControl.pdll : 2.0.0.1
205 | PluginLineProfile.pdll : 2.0.1.0
206 | PluginMobileServer.pdll : 2.0.0.0
207 | PluginMotionDetector.pdll : 2.0.0.0
208 | PluginPIVFocusMode.pdll : 2.0.0.0
209 | PluginPolarization.pdll : 2.0.0.0
210 | PluginPseudoColor.pdll : 2.0.0.0
211 |
212 | #System Information:
213 | OS : Windows 8 Professional (x64) (version 6.2 Build 9200)
214 | Memory : 262111MB RAM
215 | Processor : Intel(R) Xeon(R) CPU E5-2687W 0 @ 3.10GHz
216 | DirectX : DirectX 12
217 |
218 | END
219 |
220 | #Photron Fastcam Viewer Version Information:
221 | Saved Date : 2016/4/21
222 | Saved Time : 13:17
223 | PFV.exe : 3.6.4.0
224 | 1024PCIInst.exe : 1.1.0.0
225 | 512PCIInst.exe : 1.1.0.0
226 | node.exe : 0.10.26
227 | D1024PCI.dll : 2.0.0.0
228 | D512PCI.dll : 2.0.0.0
229 | DAPX.dll : 2.0.0.0
230 | DAPXRS.dll : 2.0.0.0
231 | DAX.dll : 2.3.0.0
232 | DAX100.dll : 2.3.0.0
233 | DAX50.dll : 2.3.0.0
234 | DBC2.dll : 2.1.0.0
235 | DIDPEX.dll : 2.0.0.1
236 | DMC1.dll : 2.1.0.0
237 | DMH4.dll : 2.1.0.0
238 | DSA1.dll : 2.1.0.0
239 | DSA2.dll : 2.1.0.0
240 | DSA3.dll : 2.1.0.0
241 | DSA4.dll : 2.1.0.0
242 | DSA5.dll : 2.1.0.0
243 | DSA6.dll : 2.1.0.0
244 | DSA7.dll : 2.1.0.0
245 | DSA8.dll : 2.1.0.0
246 | DSAX.dll : 2.2.0.0
247 | DSAX_B.dll : 2.3.0.0
248 | DSAZ.dll : 2.3.0.0
249 | DTX.dll : 2.1.0.0
250 | DUL512.dll : 2.0.0.0
251 | DUX.dll : 2.3.0.0
252 | DUX50.dll : 2.3.0.0
253 | DWX.dll : 2.2.0.0
254 | DWX50.dll : 2.2.0.0
255 | FHSDLCTRL.dll : 1.2.0.0
256 | FSDCTRL.dll : 2.1.0.0
257 | GEthLib.dll : 2.0.0.1
258 | I1394.dll : 2.0.0.0
259 | IGETHER.dll : 2.0.0.1
260 | ijl20.dll : 2,0,18,50
261 | IOPTICAL.dll : 2.0.0.0
262 | IPCI.dll : 2.0.0.0
263 | ippcce9-6.1.dll : 6,1,137,734
264 | ippccem64t-6.1.dll : 6,1,137,734
265 | ippccm7-6.1.dll : 6,1,137,734
266 | ippccmx-6.1.dll : 6,1,137,734
267 | ippccn8-6.1.dll : 6,1,137,734
268 | ippccu8-6.1.dll : 6,1,137,734
269 | ippccy8-6.1.dll : 6,1,137,734
270 | ippcoreem64t-6.1.dll : 6,1,137,790
271 | ippcve9-6.1.dll : 6,1,137,811
272 | ippcvem64t-6.1.dll : 6,1,137,811
273 | ippcvm7-6.1.dll : 6,1,137,811
274 | ippcvmx-6.1.dll : 6,1,137,811
275 | ippcvn8-6.1.dll : 6,1,137,811
276 | ippcvu8-6.1.dll : 6,1,137,811
277 | ippcvy8-6.1.dll : 6,1,137,811
278 | ippie9-6.1.dll : 6,1,137,825
279 | ippiem64t-6.1.dll : 6,1,137,825
280 | ippim7-6.1.dll : 6,1,137,825
281 | ippimx-6.1.dll : 6,1,137,825
282 | ippin8-6.1.dll : 6,1,137,825
283 | ippiu8-6.1.dll : 6,1,137,825
284 | ippiy8-6.1.dll : 6,1,137,825
285 | ippje9-6.1.dll : 6,1,137,803
286 | ippjem64t-6.1.dll : 6,1,137,803
287 | ippjm7-6.1.dll : 6,1,137,803
288 | ippjmx-6.1.dll : 6,1,137,803
289 | ippjn8-6.1.dll : 6,1,137,803
290 | ippju8-6.1.dll : 6,1,137,803
291 | ippjy8-6.1.dll : 6,1,137,803
292 | ippsce9-6.1.dll : 6,1,137,798
293 | ippscem64t-6.1.dll : 6,1,137,798
294 | ippscm7-6.1.dll : 6,1,137,798
295 | ippscmx-6.1.dll : 6,1,137,798
296 | ippscn8-6.1.dll : 6,1,137,798
297 | ippscu8-6.1.dll : 6,1,137,798
298 | ippscy8-6.1.dll : 6,1,137,798
299 | ippse9-6.1.dll : 6,1,137,827
300 | ippsem64t-6.1.dll : 6,1,137,827
301 | ippsm7-6.1.dll : 6,1,137,827
302 | ippsmx-6.1.dll : 6,1,137,827
303 | ippsn8-6.1.dll : 6,1,137,827
304 | libguide40.dll : 20071022
305 | libiomp5md.dll : 20071022
306 | libpng12.dll : ???
307 | libpng13.dll : 1.2.12
308 | PDCLIB.dll : 2.3.0.0
309 | PDCPOLA.dll : 1, 0, 1, 0
310 | PICLIB.dll : 2.2.0.0
311 | PluginCamera.dll : 1.6.1.0
312 | PluginDataSave.dll : 1.6.1.0
313 | PluginFileView.dll : 1.6.1.0
314 | zlib.dll : 1.2.7
315 | zlib1.dll : 1.2.3
316 | PluginAngle.pdll : 2.0.0.0
317 | PluginBatchDataConverter.pdll : 2.0.0.0
318 | PluginComm.pdll : 2.0.0.0
319 | PluginCycleView.pdll : 2.1.0.0
320 | PluginDistance.pdll : 2.0.0.0
321 | PluginHDR.pdll : 2.0.0.0
322 | PluginHistogram.pdll : 2.0.0.0
323 | PluginLEDLaserControl.pdll : 2.0.0.0
324 | PluginLensControl.pdll : 2.0.0.1
325 | PluginLineProfile.pdll : 2.0.1.0
326 | PluginMobileServer.pdll : 2.0.0.0
327 | PluginMotionDetector.pdll : 2.0.0.0
328 | PluginPIVFocusMode.pdll : 2.0.0.0
329 | PluginPolarization.pdll : 2.0.0.0
330 | PluginPseudoColor.pdll : 2.0.0.0
331 | PluginSyncCamera.pdll : 2.0.0.0
332 |
333 | #System Information:
334 | OS : Windows 7 Professional (x64) Service Pack 1 (version 6.1 Build 7601)
335 | Memory : 16297MB RAM
336 | Processor : Intel(R) Core(TM) i7-4610M CPU @ 3.00GHz
337 | DirectX : DirectX 11
338 |
339 | #Device Information:
340 | Number of devices : 1
341 |
342 | #Device 1 (Current):
343 | Device Name : FASTCAM SA-Z type 2100K-M-64GB
344 | Device ID : 010
345 | Interface : Gigabit Ethernet
346 | IP Address : 192.168.0.10
347 | Subnet Mask : 255.255.255.0
348 | Gateway Address : 0.0.0.0
349 | Firmware : 1.07
350 |
351 | END
--------------------------------------------------------------------------------
/pyMRAW.py:
--------------------------------------------------------------------------------
1 | # www.ladisk.si
2 | #
3 | # pyMRAW is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, version 3 of the License.
6 | #
7 | # pyMRAW is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU General Public License
13 | # along with pyMRAW. If not, see .
14 | """
15 | This module is reads Photron MRAW image sequences.
16 |
17 | Author: Jaka Javh (jaka.javh@fs.uni-lj.si), Janko Slavič (janko.slavic@fs.uni-lj.si) www.ladisk.si
18 |
19 | We developed this module while working on this publication:
20 | J. Javh, J. Slavič and M. Boltežar: The Subpixel Resolution of Optical-Flow-Based Modal Analysis,
21 | Mechanical Systems and Signal Processing, Vol. 88, p. 89–99, 2017
22 |
23 | If you find it useful, consider to cite us.
24 | """
25 |
26 | import os
27 | from os import path
28 | import numpy as np
29 | import numba as nb
30 | import warnings
31 | import xmltodict
32 |
33 | __version__ = "0.33.0"
34 |
35 | SUPPORTED_FILE_FORMATS = ['mraw', 'tiff']
36 | SUPPORTED_EFFECTIVE_BIT_SIDE = ['lower', 'higher']
37 |
38 |
39 | def get_cih(filename):
40 | name, ext = path.splitext(filename)
41 | if ext == '.cih':
42 | cih = dict()
43 | # read the cif header
44 | with open(filename, 'r') as f:
45 | for line in f:
46 | if line == '\n': #end of cif header
47 | break
48 | line_sp = line.replace('\n', '').split(' : ')
49 | if len(line_sp) == 2:
50 | key, value = line_sp
51 | try:
52 | if '.' in value:
53 | value = float(value)
54 | else:
55 | value = int(value)
56 | cih[key] = value
57 | except:
58 | cih[key] = value
59 |
60 | elif ext == '.cihx':
61 | with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
62 | lines = f.readlines()
63 | first_last_line = [ i for i in range(len(lines)) if '' in lines[i] or '' in lines[i] ]
64 | xml = ''.join(lines[first_last_line[0]:first_last_line[-1]+1])
65 |
66 | raw_cih_dict = xmltodict.parse(xml)
67 | cih = {
68 | 'Date': raw_cih_dict['cih']['fileInfo']['date'],
69 | 'Camera Type': raw_cih_dict['cih']['deviceInfo']['deviceName'],
70 | 'Record Rate(fps)': float(raw_cih_dict['cih']['recordInfo']['recordRate']),
71 | 'Shutter Speed(s)': float(raw_cih_dict['cih']['recordInfo']['shutterSpeed']),
72 | 'Total Frame': int(raw_cih_dict['cih']['frameInfo']['totalFrame']),
73 | 'Original Total Frame': int(raw_cih_dict['cih']['frameInfo']['recordedFrame']),
74 | 'Image Width': int(raw_cih_dict['cih']['imageDataInfo']['resolution']['width']),
75 | 'Image Height': int(raw_cih_dict['cih']['imageDataInfo']['resolution']['height']),
76 | 'File Format': raw_cih_dict['cih']['imageFileInfo']['fileFormat'],
77 | 'EffectiveBit Depth': int(raw_cih_dict['cih']['imageDataInfo']['effectiveBit']['depth']),
78 | 'EffectiveBit Side': raw_cih_dict['cih']['imageDataInfo']['effectiveBit']['side'],
79 | 'Color Bit': int(raw_cih_dict['cih']['imageDataInfo']['colorInfo']['bit']),
80 | 'Comment Text': raw_cih_dict['cih']['basicInfo'].get('comment', ''),
81 | }
82 |
83 | else:
84 | raise Exception('Unsupported configuration file ({:s})!'.format(ext))
85 |
86 | return cih
87 |
88 |
89 | def load_images(mraw, h, w, N, bit=16, roll_axis=True):
90 | """
91 | loads the next N images from the binary mraw file into a numpy array.
92 | Inputs:
93 | mraw: an opened binary .mraw file
94 | h: image height
95 | w: image width
96 | N: number of sequential images to be loaded
97 | roll_axis (bool): whether to roll the first axis of the output
98 | to the back or not. Defaults to True
99 | Outputs:
100 | images: array of shape (h, w, N) if `roll_axis` is True, or (N, h, w) otherwise.
101 | """
102 |
103 | if int(bit) == 16:
104 | images = np.memmap(mraw, dtype=np.uint16, mode='r', shape=(N, h, w))
105 | elif int(bit) == 8:
106 | images = np.memmap(mraw, dtype=np.uint8, mode='r', shape=(N, h, w))
107 | elif int(bit) == 12:
108 | # warnings.warn("12bit images will be loaded into memory!")
109 | #images = _read_uint12_video(mraw, (N, h, w))
110 | images = _read_uint12_video_prec(mraw, (N, h, w))
111 | else:
112 | raise Exception(f"Unsupported bit depth: {bit}")
113 |
114 |
115 | #images=np.fromfile(mraw, dtype=np.uint16, count=h * w * N).reshape(N, h, w) # about a 1/3 slower than memmap when loading to RAM. Also memmap doesn't need to read to RAM but can read from disc when needed.
116 | if roll_axis:
117 | return np.rollaxis(images, 0, 3)
118 | else:
119 | return images
120 |
121 | def check_exceptions(cih):
122 | # check exceptions
123 | ff = cih['File Format']
124 | if ff.lower() not in SUPPORTED_FILE_FORMATS:
125 | raise Exception('Unexpected File Format: {:g}.'.format(ff))
126 | # bits = cih['Color Bit']
127 | # if bits < 12:
128 | # warnings.warn('Not 12bit ({:g} bits)! clipped values?'.format(bits))
129 | # # - may cause overflow')
130 | # # 12-bit values are spaced over the 16bit resolution - in case of photron filming at 12bit
131 | # # this can be meanded by dividing images with //16
132 | if cih['EffectiveBit Depth'] != 12:
133 | warnings.warn('Not 12bit image!')
134 | ebs = cih['EffectiveBit Side']
135 | if ebs.lower() not in SUPPORTED_EFFECTIVE_BIT_SIDE:
136 | raise Exception('Unexpected EffectiveBit Side: {:g}'.format(ebs))
137 | if (cih['File Format'].lower() == 'mraw') & (cih['Color Bit'] not in [8, 12, 16]):
138 | raise Exception('pyMRAW only works for 8-bit, 12-bit and 16-bit files!')
139 | # if cih['Original Total Frame'] > cih['Total Frame']:
140 | # warnings.warn('Clipped footage! (Total frame: {}, Original total frame: {})'.format(cih['Total Frame'], cih['Original Total Frame'] ))
141 |
142 | def load_video(cih_file):
143 | """
144 | Loads and returns images and a cih info dict.
145 |
146 | Inputs:
147 | cih_filename: path to .cih or .cihx file, with a .mraw file
148 | with the same name in the same folder.
149 | Outputs:
150 | images: image data array of shape (N, h, w)
151 | cih: cih info dict.
152 |
153 | """
154 | cih = get_cih(cih_file)
155 | check_exceptions(cih)
156 |
157 | mraw_file = path.splitext(cih_file)[0] + '.mraw'
158 | N = cih['Total Frame']
159 | h = cih['Image Height']
160 | w = cih['Image Width']
161 | bit = cih['Color Bit']
162 | images = load_images(mraw_file, h, w, N, bit, roll_axis=False)
163 | return images, cih
164 |
165 |
166 | def save_mraw(images, save_path, bit_depth=16, ext='mraw', info_dict={}):
167 | """
168 | Saves given sequence of images into .mraw file.
169 |
170 | Inputs:
171 | sequence : array_like of shape (n, h, w), sequence of `n` grayscale images
172 | of shape (h, w) to save.
173 | save_path : str, path to saved cih file.
174 | bit_depth: int, bit depth of the image data. Currently supported bit depths are 8 and 16.
175 | ext : str, generated file extension ('mraw' or 'npy'). If set to 'mraw', it can be viewed in
176 | PFV. Defaults to '.mraw'.
177 | info_dict : dict, mraw video information to go into the .cih file. The info keys have to match
178 | .cih properties descriptions exactly (example common keys: 'Record Rate(fps)',
179 | 'Shutter Speed(s)', 'Comment Text' etc.).
180 |
181 | Outputs:
182 | mraw_path : str, path to output or .mraw (or .npy) file.
183 | cih_path : str, path to generated .cih file
184 | """
185 |
186 | filename, extension = path.splitext(save_path)
187 | mraw_path = '{:s}.{:s}'.format(filename, ext)
188 | cih_path = '{:s}.{:s}'.format(filename, '.cih')
189 |
190 | directory_path = path.split(save_path)[0]
191 | if not path.exists(directory_path):
192 | os.makedirs(directory_path)
193 |
194 | bit_depth_dtype_map = {
195 | 8: np.uint8,
196 | 16: np.uint16
197 | }
198 | if bit_depth not in bit_depth_dtype_map.keys():
199 | raise ValueError('Currently supported bit depths are 8 and 16.')
200 |
201 | if bit_depth < 16:
202 | effective_bit = bit_depth
203 | else:
204 | effective_bit = 12
205 | if np.max(images) > 2**bit_depth-1:
206 | raise ValueError(
207 | 'The input image data does not match the selected bit depth. ' +
208 | 'Consider normalizing the image data before saving.')
209 |
210 | # Generate .mraw file
211 | with open(mraw_path, 'wb') as file:
212 | for image in images:
213 | image = image.astype(bit_depth_dtype_map[bit_depth])
214 | image.tofile(file)
215 | file_shape = (int(len(images)), image.shape[0], image.shape[1])
216 | file_format = 'MRaw'
217 |
218 | image_info = {'Record Rate(fps)': '{:d}'.format(1),
219 | 'Shutter Speed(s)': '{:.6f}'.format(1),
220 | 'Total Frame': '{:d}'.format(file_shape[0]),
221 | 'Original Total Frame': '{:d}'.format(file_shape[0]),
222 | 'Start Frame': '{:d}'.format(0),
223 | 'Image Width': '{:d}'.format(file_shape[2]),
224 | 'Image Height': '{:d}'.format(file_shape[1]),
225 | 'Color Type': 'Mono',
226 | 'Color Bit': bit_depth,
227 | 'File Format' : file_format,
228 | 'EffectiveBit Depth': effective_bit,
229 | 'Comment Text': 'Generated sequence. Modify measurement info in created .cih file if necessary.',
230 | 'EffectiveBit Side': 'Lower'}
231 |
232 | image_info.update(info_dict)
233 |
234 | cih_path = '{:s}.{:s}'.format(filename, 'cih')
235 | with open(cih_path, 'w') as file:
236 | file.write('#Camera Information Header\n')
237 | for key in image_info.keys():
238 | file.write('{:s} : {:s}\n'.format(key, str(image_info[key])))
239 |
240 | return mraw_path, cih_path
241 |
242 | def _read_uint12_video(data, shape):
243 | """Utility function to read 12bit packed mraw files into uint16 array
244 | Will store entire array in memory!
245 |
246 | Adapted from https://stackoverflow.com/a/51967333/9173710
247 | """
248 | data = np.memmap(data, dtype=np.uint8, mode="r")
249 | fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
250 | fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4)
251 | snd_uint12 = ((mid_uint8 % 16) << 8) + lst_uint8
252 | return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), shape)
253 |
254 | def _read_uint12_video_prec(data, shape):
255 | """Utility function to read 12bit packed mraw files into uint16 array
256 | Will store entire array in memory!
257 |
258 | Adapted from https://stackoverflow.com/a/51967333/9173710
259 | """
260 | data = np.memmap(data, dtype=np.uint8, mode="r")
261 | return nb_read_uint12(data).reshape(shape)
262 |
263 |
264 | @nb.njit(nb.uint16[::1](nb.types.Array(nb.types.uint8, 1, 'C', readonly=True)), fastmath=True, parallel=True, cache=True)
265 | def nb_read_uint12(data_chunk):
266 | """ precompiled function to efficiently covnert from 12bit packed video to 16bit video
267 | it splits 3 bytes into two 16 bit words
268 | data_chunk is a contigous 1D array of uint8 data, e.g. the 12bit video loaded as 8bit array
269 | """
270 |
271 | #ensure that the data_chunk has the right length
272 | assert np.mod(data_chunk.shape[0],3)==0
273 | out = np.empty(data_chunk.size//3*2, dtype=np.uint16)
274 |
275 | for i in nb.prange(data_chunk.shape[0]//3):
276 | fst_uint8=np.uint16(data_chunk[i*3])
277 | mid_uint8=np.uint16(data_chunk[i*3+1])
278 | lst_uint8=np.uint16(data_chunk[i*3+2])
279 |
280 | out[i*2] = (fst_uint8 << 4) + (mid_uint8 >> 4)
281 | out[i*2+1] = ((mid_uint8 % 16) << 8) + lst_uint8
282 |
283 | return out
284 |
285 | def show_UI():
286 | from tkinter import Tk
287 | from tkinter.filedialog import askopenfilename
288 | from matplotlib import pyplot as plt
289 | from matplotlib import animation
290 |
291 |
292 | window = Tk() # open window
293 | filename = askopenfilename(parent=window, title='Select the .cih file', filetypes=[
294 | ("Photron cih file", "*.cih"), ("Photron cihx file", "*.cihx")]) # open window to load the camera and files info
295 | window.destroy() # close the tk window
296 |
297 | cih = get_cih(filename)
298 | check_exceptions(cih)
299 |
300 | N = cih['Total Frame']
301 | h = cih['Image Height']
302 | w = cih['Image Width']
303 |
304 | #if N > 12:
305 | # N = 12
306 | name, ext = path.splitext(filename)
307 | mraw = open(name + '.mraw', 'rb')
308 | mraw.seek(0, 0) # find the beginning of the file
309 | image_data = load_images(mraw, h, w, N) # load N images
310 | #np.memmap in load_images loads enables reading an array from disc as if from RAM. If you want all the images to load on RAM imediatly use load_images(mraw, h, w, N).copy()
311 | mraw.close()
312 |
313 | fig = plt.figure()
314 | ax = plt.subplot()
315 | ms = ax.matshow(image_data[:, :, 0], cmap=plt.get_cmap('gray'), vmin=0,
316 | vmax=2 ** 12) # display data for first image
317 |
318 |
319 | def animate(i):
320 | ms.set_data(image_data[:, :, i])
321 | return [ms]
322 |
323 |
324 | anim = animation.FuncAnimation(fig, animate, frames=N, interval=1, blit=True)
325 | plt.show()
326 |
327 |
328 | if __name__ == '__main__':
329 | show_UI()
330 | #a = get_cih('data/sample_60k_16bit.cih')
331 | #print(a)
--------------------------------------------------------------------------------
/Showcase.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import numpy as np\n",
10 | "import matplotlib.pyplot as plt\n",
11 | "import pyMRAW\n",
12 | "import os"
13 | ]
14 | },
15 | {
16 | "cell_type": "markdown",
17 | "metadata": {},
18 | "source": [
19 | "# pyMRAW functionality showcase\n",
20 | "\n",
21 | "`pyMRAW` is an open-source package, enabling the efficient use of the Photron MRAW video files in Python workflows.\n",
22 | "\n",
23 | "It's main feature is the use of memory-mapped ([`np.memmap`](https://numpy.org/doc/stable/reference/generated/numpy.memmap.html)) arrays to create memory maps to locally stored raw video files and avoid loading large amounts of data into RAM. \n",
24 | "\n",
25 | "**Warning**: to take advantage of pyMRAW's memory-mapping functionality, make sure to save MRAW files either in 8-bit or 16-bit formats, corresponding to standard data types `uint8` and `uint16`! Using pyMRAW to read 12-bit MRAW files is possible, but requires loading the complete image data into RAM to produce standard Numpy arrays."
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "## Working with MRAW files"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "MRAW files are stored along with `cihx` (or `cih` in legacy applications) metadata files. These contain vital information for interpretation of the binary image data. and must therefore be present alongside the MRAW files.\n",
40 | "\n",
41 | "To load `.mraw` - `.cihx` files, use the `pymraw.load_video` function (if the video was cut from its original size during saving, a warning will be shown on first load, and can be safely ignored):"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": 2,
47 | "metadata": {},
48 | "outputs": [
49 | {
50 | "name": "stderr",
51 | "output_type": "stream",
52 | "text": [
53 | "d:\\workspace\\_PY_PACKAGES\\pymraw\\pyMRAW.py:104: UserWarning: Clipped footage! (Total frame: 4, Original total frame: 400)\n",
54 | " warnings.warn('Clipped footage! (Total frame: {}, Original total frame: {})'.format(cih['Total Frame'], cih['Original Total Frame'] ))\n"
55 | ]
56 | }
57 | ],
58 | "source": [
59 | "images, info = pyMRAW.load_video('data/beam.cihx')"
60 | ]
61 | },
62 | {
63 | "cell_type": "markdown",
64 | "metadata": {},
65 | "source": [
66 | "In case of 16-bit image data, the `video` object is a memory-mapped array of the `uint16` data type and shape `(N_images, height, width)`:"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": 3,
72 | "metadata": {},
73 | "outputs": [
74 | {
75 | "data": {
76 | "text/plain": [
77 | "(numpy.memmap, dtype('uint16'), (4, 80, 1024))"
78 | ]
79 | },
80 | "execution_count": 3,
81 | "metadata": {},
82 | "output_type": "execute_result"
83 | }
84 | ],
85 | "source": [
86 | "type(images), images.dtype, images.shape"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "metadata": {},
92 | "source": [
93 | "The `info` object is a dictionary of metadata, read from the `cihx` file, which can also be easily viewed in Python:"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": 4,
99 | "metadata": {},
100 | "outputs": [
101 | {
102 | "data": {
103 | "text/plain": [
104 | "{'Date': '2017/7/13',\n",
105 | " 'Camera Type': 'FASTCAM SA-Z type 2100K-M-64GB',\n",
106 | " 'Record Rate(fps)': 50000.0,\n",
107 | " 'Shutter Speed(s)': 200000.0,\n",
108 | " 'Total Frame': 4,\n",
109 | " 'Original Total Frame': 400,\n",
110 | " 'Image Width': 1024,\n",
111 | " 'Image Height': 80,\n",
112 | " 'File Format': 'Mraw',\n",
113 | " 'EffectiveBit Depth': 12,\n",
114 | " 'EffectiveBit Side': 'Lower',\n",
115 | " 'Color Bit': 16,\n",
116 | " 'Comment Text': None}"
117 | ]
118 | },
119 | "execution_count": 4,
120 | "metadata": {},
121 | "output_type": "execute_result"
122 | }
123 | ],
124 | "source": [
125 | "info"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "metadata": {},
131 | "source": [
132 | "The images, contained in the `video` memory-mapped array, can be used in the same way as normal Numpy array objects.They will be accepted as input into any workflows where a Numpy array would be accepted."
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "execution_count": 5,
138 | "metadata": {},
139 | "outputs": [
140 | {
141 | "data": {
142 | "text/plain": [
143 | "(numpy.memmap, dtype('uint16'), (80, 1024))"
144 | ]
145 | },
146 | "execution_count": 5,
147 | "metadata": {},
148 | "output_type": "execute_result"
149 | }
150 | ],
151 | "source": [
152 | "first_image = images[0]\n",
153 | "type(first_image), first_image.dtype, first_image.shape"
154 | ]
155 | },
156 | {
157 | "cell_type": "code",
158 | "execution_count": 7,
159 | "metadata": {},
160 | "outputs": [
161 | {
162 | "data": {
163 | "image/png": "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",
164 | "text/plain": [
165 | ""
166 | ]
167 | },
168 | "metadata": {},
169 | "output_type": "display_data"
170 | }
171 | ],
172 | "source": [
173 | "plt.figure()\n",
174 | "plt.imshow(first_image, cmap='gray')\n",
175 | "plt.show()"
176 | ]
177 | },
178 | {
179 | "cell_type": "markdown",
180 | "metadata": {},
181 | "source": [
182 | "Some caution is advised when processing the `memmap` arrays to avoid operations that would convert the data into conventional Numpy arrays, loading it into RAM. Especially when using non-Numpy methods, make sure that the output of your processing workflow is still a memory-mapped array before committing to processing large quantities of data."
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "metadata": {},
188 | "source": [
189 | "## Saving MRAW videos"
190 | ]
191 | },
192 | {
193 | "cell_type": "markdown",
194 | "metadata": {},
195 | "source": [
196 | "`pyMRAW` also includes the basic functionality of saving your custom image data into the MRAW format using the `pyMRAW.save_mraw()` method:"
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": 29,
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "random_images = np.random.randint(low=0, high=255, size=(10, 64, 64), dtype=int)\n",
206 | "info_dict = {'Record Rate(fps)': 25,\n",
207 | " 'Total Frame': random_images.shape[0],\n",
208 | " 'Image Width': random_images.shape[2],\n",
209 | " 'Image Height': random_images.shape[1],\n",
210 | " 'Color Type': 'Mono', \n",
211 | " 'Color Bit': 8,\n",
212 | " 'Comment Text': 'Randomly generated images.',\n",
213 | " }\n",
214 | "\n",
215 | "os.makedirs('temp', exist_ok=True)\n",
216 | "mraw_file, cih_file = pyMRAW.save_mraw(random_images, 'temp/random.mraw', info_dict=info_dict)"
217 | ]
218 | },
219 | {
220 | "cell_type": "markdown",
221 | "metadata": {},
222 | "source": [
223 | " The expected image format is an appropriately shaped, `(N_images, height, width)`, integer Numpy array.\n",
224 | "\n",
225 | " The `info_dict` argument is a dictionary of metadata you wish to assign to your video. Default values for key metadata entries, required to view the MRAW video using Photron's PFV software, are used if they are not provided via `info_dict`."
226 | ]
227 | }
228 | ],
229 | "metadata": {
230 | "kernelspec": {
231 | "display_name": "Python 3",
232 | "language": "python",
233 | "name": "python3"
234 | },
235 | "language_info": {
236 | "codemirror_mode": {
237 | "name": "ipython",
238 | "version": 3
239 | },
240 | "file_extension": ".py",
241 | "mimetype": "text/x-python",
242 | "name": "python",
243 | "nbconvert_exporter": "python",
244 | "pygments_lexer": "ipython3",
245 | "version": "3.12.2"
246 | }
247 | },
248 | "nbformat": 4,
249 | "nbformat_minor": 2
250 | }
251 |
--------------------------------------------------------------------------------