├── .github └── workflows │ ├── python-package.yml │ └── release-and-publish-to-pypi.yml ├── .gitignore ├── LICENSE ├── Showcase.ipynb ├── data ├── ball_12bit.cihx ├── ball_12bit.mraw ├── beam.cihx ├── beam.mraw ├── sample_60k_16bit.cih └── sample_60k_16bit.mraw ├── distribution.bat ├── pyMRAW.py ├── pyproject.toml ├── readme.rst ├── requirements.dev.txt ├── requirements.txt ├── setup.py └── tests └── test_all.py /.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 -------------------------------------------------------------------------------- /.github/workflows/release-and-publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Release and Publish to PyPI 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.11' 17 | 18 | - name: Install dependencies 19 | run: pip install -U build 20 | 21 | - name: Build package 22 | run: python -m build 23 | 24 | - name: Publish to PyPI 25 | uses: pypa/gh-action-pypi-publish@v1.4.2 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data/ball_12bit.cihx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ladisk/pyMRAW/8d38087a4f856f9d23e9ed75b8b8def3dafb41fb/data/ball_12bit.cihx -------------------------------------------------------------------------------- /data/ball_12bit.mraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ladisk/pyMRAW/8d38087a4f856f9d23e9ed75b8b8def3dafb41fb/data/ball_12bit.mraw -------------------------------------------------------------------------------- /data/beam.cihx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ladisk/pyMRAW/8d38087a4f856f9d23e9ed75b8b8def3dafb41fb/data/beam.cihx -------------------------------------------------------------------------------- /data/beam.mraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ladisk/pyMRAW/8d38087a4f856f9d23e9ed75b8b8def3dafb41fb/data/beam.mraw -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /data/sample_60k_16bit.mraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ladisk/pyMRAW/8d38087a4f856f9d23e9ed75b8b8def3dafb41fb/data/sample_60k_16bit.mraw -------------------------------------------------------------------------------- /distribution.bat: -------------------------------------------------------------------------------- 1 | python setup.py sdist bdist_wheel 2 | twine upload dist/* -------------------------------------------------------------------------------- /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.32.1' 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 | # check exceptions 87 | ff = cih['File Format'] 88 | if ff.lower() not in SUPPORTED_FILE_FORMATS: 89 | raise Exception('Unexpected File Format: {:g}.'.format(ff)) 90 | # bits = cih['Color Bit'] 91 | # if bits < 12: 92 | # warnings.warn('Not 12bit ({:g} bits)! clipped values?'.format(bits)) 93 | # # - may cause overflow') 94 | # # 12-bit values are spaced over the 16bit resolution - in case of photron filming at 12bit 95 | # # this can be meanded by dividing images with //16 96 | if cih['EffectiveBit Depth'] != 12: 97 | warnings.warn('Not 12bit image!') 98 | ebs = cih['EffectiveBit Side'] 99 | if ebs.lower() not in SUPPORTED_EFFECTIVE_BIT_SIDE: 100 | raise Exception('Unexpected EffectiveBit Side: {:g}'.format(ebs)) 101 | if (cih['File Format'].lower() == 'mraw') & (cih['Color Bit'] not in [8, 12, 16]): 102 | raise Exception('pyMRAW only works for 8-bit, 12-bit and 16-bit files!') 103 | # if cih['Original Total Frame'] > cih['Total Frame']: 104 | # warnings.warn('Clipped footage! (Total frame: {}, Original total frame: {})'.format(cih['Total Frame'], cih['Original Total Frame'] )) 105 | 106 | return cih 107 | 108 | 109 | def load_images(mraw, h, w, N, bit=16, roll_axis=True): 110 | """ 111 | loads the next N images from the binary mraw file into a numpy array. 112 | Inputs: 113 | mraw: an opened binary .mraw file 114 | h: image height 115 | w: image width 116 | N: number of sequential images to be loaded 117 | roll_axis (bool): whether to roll the first axis of the output 118 | to the back or not. Defaults to True 119 | Outputs: 120 | images: array of shape (h, w, N) if `roll_axis` is True, or (N, h, w) otherwise. 121 | """ 122 | 123 | if int(bit) == 16: 124 | images = np.memmap(mraw, dtype=np.uint16, mode='r', shape=(N, h, w)) 125 | elif int(bit) == 8: 126 | images = np.memmap(mraw, dtype=np.uint8, mode='r', shape=(N, h, w)) 127 | elif int(bit) == 12: 128 | # warnings.warn("12bit images will be loaded into memory!") 129 | #images = _read_uint12_video(mraw, (N, h, w)) 130 | images = _read_uint12_video_prec(mraw, (N, h, w)) 131 | else: 132 | raise Exception(f"Unsupported bit depth: {bit}") 133 | 134 | 135 | #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. 136 | if roll_axis: 137 | return np.rollaxis(images, 0, 3) 138 | else: 139 | return images 140 | 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 | mraw_file = path.splitext(cih_file)[0] + '.mraw' 156 | N = cih['Total Frame'] 157 | h = cih['Image Height'] 158 | w = cih['Image Width'] 159 | bit = cih['Color Bit'] 160 | images = load_images(mraw_file, h, w, N, bit, roll_axis=False) 161 | return images, cih 162 | 163 | 164 | def save_mraw(images, save_path, bit_depth=16, ext='mraw', info_dict={}): 165 | """ 166 | Saves given sequence of images into .mraw file. 167 | 168 | Inputs: 169 | sequence : array_like of shape (n, h, w), sequence of `n` grayscale images 170 | of shape (h, w) to save. 171 | save_path : str, path to saved cih file. 172 | bit_depth: int, bit depth of the image data. Currently supported bit depths are 8 and 16. 173 | ext : str, generated file extension ('mraw' or 'npy'). If set to 'mraw', it can be viewed in 174 | PFV. Defaults to '.mraw'. 175 | info_dict : dict, mraw video information to go into the .cih file. The info keys have to match 176 | .cih properties descriptions exactly (example common keys: 'Record Rate(fps)', 177 | 'Shutter Speed(s)', 'Comment Text' etc.). 178 | 179 | Outputs: 180 | mraw_path : str, path to output or .mraw (or .npy) file. 181 | cih_path : str, path to generated .cih file 182 | """ 183 | 184 | filename, extension = path.splitext(save_path) 185 | mraw_path = '{:s}.{:s}'.format(filename, ext) 186 | cih_path = '{:s}.{:s}'.format(filename, '.cih') 187 | 188 | directory_path = path.split(save_path)[0] 189 | if not path.exists(directory_path): 190 | os.makedirs(directory_path) 191 | 192 | bit_depth_dtype_map = { 193 | 8: np.uint8, 194 | 16: np.uint16 195 | } 196 | if bit_depth not in bit_depth_dtype_map.keys(): 197 | raise ValueError('Currently supported bit depths are 8 and 16.') 198 | 199 | if bit_depth < 16: 200 | effective_bit = bit_depth 201 | else: 202 | effective_bit = 12 203 | if np.max(images) > 2**bit_depth-1: 204 | raise ValueError( 205 | 'The input image data does not match the selected bit depth. ' + 206 | 'Consider normalizing the image data before saving.') 207 | 208 | # Generate .mraw file 209 | with open(mraw_path, 'wb') as file: 210 | for image in images: 211 | image = image.astype(bit_depth_dtype_map[bit_depth]) 212 | image.tofile(file) 213 | file_shape = (int(len(images)), image.shape[0], image.shape[1]) 214 | file_format = 'MRaw' 215 | 216 | image_info = {'Record Rate(fps)': '{:d}'.format(1), 217 | 'Shutter Speed(s)': '{:.6f}'.format(1), 218 | 'Total Frame': '{:d}'.format(file_shape[0]), 219 | 'Original Total Frame': '{:d}'.format(file_shape[0]), 220 | 'Start Frame': '{:d}'.format(0), 221 | 'Image Width': '{:d}'.format(file_shape[2]), 222 | 'Image Height': '{:d}'.format(file_shape[1]), 223 | 'Color Type': 'Mono', 224 | 'Color Bit': bit_depth, 225 | 'File Format' : file_format, 226 | 'EffectiveBit Depth': effective_bit, 227 | 'Comment Text': 'Generated sequence. Modify measurement info in created .cih file if necessary.', 228 | 'EffectiveBit Side': 'Lower'} 229 | 230 | image_info.update(info_dict) 231 | 232 | cih_path = '{:s}.{:s}'.format(filename, 'cih') 233 | with open(cih_path, 'w') as file: 234 | file.write('#Camera Information Header\n') 235 | for key in image_info.keys(): 236 | file.write('{:s} : {:s}\n'.format(key, str(image_info[key]))) 237 | 238 | return mraw_path, cih_path 239 | 240 | def _read_uint12_video(data, shape): 241 | """Utility function to read 12bit packed mraw files into uint16 array 242 | Will store entire array in memory! 243 | 244 | Adapted from https://stackoverflow.com/a/51967333/9173710 245 | """ 246 | data = np.memmap(data, dtype=np.uint8, mode="r") 247 | fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T 248 | fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4) 249 | snd_uint12 = ((mid_uint8 % 16) << 8) + lst_uint8 250 | return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), shape) 251 | 252 | def _read_uint12_video_prec(data, shape): 253 | """Utility function to read 12bit packed mraw files into uint16 array 254 | Will store entire array in memory! 255 | 256 | Adapted from https://stackoverflow.com/a/51967333/9173710 257 | """ 258 | data = np.memmap(data, dtype=np.uint8, mode="r") 259 | return nb_read_uint12(data).reshape(shape) 260 | 261 | 262 | @nb.njit(nb.uint16[::1](nb.types.Array(nb.types.uint8, 1, 'C', readonly=True)), fastmath=True, parallel=True, cache=True) 263 | def nb_read_uint12(data_chunk): 264 | """ precompiled function to efficiently covnert from 12bit packed video to 16bit video 265 | it splits 3 bytes into two 16 bit words 266 | data_chunk is a contigous 1D array of uint8 data, e.g. the 12bit video loaded as 8bit array 267 | """ 268 | 269 | #ensure that the data_chunk has the right length 270 | assert np.mod(data_chunk.shape[0],3)==0 271 | out = np.empty(data_chunk.size//3*2, dtype=np.uint16) 272 | 273 | for i in nb.prange(data_chunk.shape[0]//3): 274 | fst_uint8=np.uint16(data_chunk[i*3]) 275 | mid_uint8=np.uint16(data_chunk[i*3+1]) 276 | lst_uint8=np.uint16(data_chunk[i*3+2]) 277 | 278 | out[i*2] = (fst_uint8 << 4) + (mid_uint8 >> 4) 279 | out[i*2+1] = ((mid_uint8 % 16) << 8) + lst_uint8 280 | 281 | return out 282 | 283 | def show_UI(): 284 | from tkinter import Tk 285 | from tkinter.filedialog import askopenfilename 286 | from matplotlib import pyplot as plt 287 | from matplotlib import animation 288 | 289 | 290 | window = Tk() # open window 291 | filename = askopenfilename(parent=window, title='Select the .cih file', filetypes=[ 292 | ("Photron cih file", "*.cih"), ("Photron cihx file", "*.cihx")]) # open window to load the camera and files info 293 | window.destroy() # close the tk window 294 | 295 | cih = get_cih(filename) 296 | N = cih['Total Frame'] 297 | h = cih['Image Height'] 298 | w = cih['Image Width'] 299 | 300 | #if N > 12: 301 | # N = 12 302 | name, ext = path.splitext(filename) 303 | mraw = open(name + '.mraw', 'rb') 304 | mraw.seek(0, 0) # find the beginning of the file 305 | image_data = load_images(mraw, h, w, N) # load N images 306 | #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() 307 | mraw.close() 308 | 309 | fig = plt.figure() 310 | ax = plt.subplot() 311 | ms = ax.matshow(image_data[:, :, 0], cmap=plt.get_cmap('gray'), vmin=0, 312 | vmax=2 ** 12) # display data for first image 313 | 314 | 315 | def animate(i): 316 | ms.set_data(image_data[:, :, i]) 317 | return [ms] 318 | 319 | 320 | anim = animation.FuncAnimation(fig, animate, frames=N, interval=1, blit=True) 321 | plt.show() 322 | 323 | 324 | if __name__ == '__main__': 325 | show_UI() 326 | #a = get_cih('data/sample_60k_16bit.cih') 327 | #print(a) -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "pyMRAW" 7 | version = "0.32.1" 8 | authors = [{name = "Jaka Javh, Janko Slavič, Domen Gorjup", email = "janko.slavic@fs.uni-lj.si"}] 9 | maintainers = [{name = "Janko Slavič et al.", email = "janko.slavic@fs.uni-lj.si"}] 10 | license = "MIT" 11 | description = "Module for reading and writing Photron MRAW image sequences." 12 | readme = "readme.rst" 13 | keywords = ["read/write", "Photron", "mraw", "cihx", "cih"] 14 | requires-python = ">=3.10" 15 | dependencies = [ 16 | "colorama>=0.3.7", 17 | "nose>=1.3.7", 18 | "numpy>=1.12.0", 19 | "py>=1.4.32", 20 | "xmltodict>=0.12.0", 21 | "numba>=0.56.4", 22 | ] 23 | classifiers = [ 24 | 'Development Status :: 5 - Production/Stable', 25 | 'Intended Audience :: Developers', 26 | 'Topic :: Scientific/Engineering', 27 | 'Programming Language :: Python :: 3.10', 28 | "License :: OSI Approved :: MIT License", 29 | ] 30 | 31 | [project.optional-dependencies] 32 | dev = [ 33 | "sphinx", 34 | "twine", 35 | "wheel", 36 | "build", 37 | "pytest", 38 | "sphinx-rtd-theme", 39 | "sphinx-copybutton>=0.5.2", 40 | ] 41 | 42 | [project.urls] 43 | homepage = "https://github.com/ladisk/pyMRAW" 44 | documentation = "https://github.com/ladisk/pyMRAW" 45 | source = "https://github.com/ladisk/pyMRAW" 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | ) -------------------------------------------------------------------------------- /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() --------------------------------------------------------------------------------