diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..2bee389 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,34 @@ + +name: Build Python Wheels +on: + schedule: + push: + branches: + - master +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, windows-2019, macOS-10.15] + arch: [auto32, auto64, aarch64, ppc64le, s390x] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.1.2 + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse + + upload_wheels: + name: Upload binary wheels to PyPI + needs: [build_wheels] + + steps: + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.IMCTERMITE_GITHUB_WORKFLOW_PYPI_API_TOKEN }} + diff --git a/.gitignore b/.gitignore index 7b52ab7..3409c74 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,13 @@ pip/LICENSE pip/*egg-info pip/dist/ pip/build/ + +python/README.md +python/LICENSE +python/VERSION +python/build +python/*.egg-info +python/dist +python/*.soc +python/lib/ +python/*.cpp diff --git a/README.md b/README.md index a3110ae..6425ade 100644 --- a/README.md +++ b/README.md @@ -211,9 +211,9 @@ channels = imcraw.get_channels(False) print(channels) ``` -A more complete [example](python/usage.py), including the methods for obtaining the -channels, i.a. their data and/or directly printing them to files, can be found -in the Python folder. +A more complete [example](python/examples/usage.py), including the methods for +obtaining the channels, i.a. their data and/or directly printing them to files, +can be found in the `python/examples` folder. ## References diff --git a/python/IMCtermite.pxd b/python/IMCtermite.pxd new file mode 100644 index 0000000..f76521e --- /dev/null +++ b/python/IMCtermite.pxd @@ -0,0 +1,24 @@ + +# use some C++ STL libraries +from libcpp.string cimport string +from libcpp.vector cimport vector +from libcpp cimport bool + +cdef extern from "lib/imc_raw.hpp" namespace "imc": + + cdef cppclass cppimctermite "imc::raw": + + # constructor(s) + cppimctermite() except + + cppimctermite(string rawfile) except + + + # provide raw file + void set_file(string rawfile) except + + + # get JSON list of channels + vector[string] get_channels(bool json, bool data) except + + + # print single channel/all channels + void print_channel(string channeluuid, string outputdir, char delimiter) except + + void print_channels(string outputdir, char delimiter) except + + void print_table(string outputfile) except + diff --git a/python/IMCtermite.pyx b/python/IMCtermite.pyx new file mode 100644 index 0000000..f4ab185 --- /dev/null +++ b/python/IMCtermite.pyx @@ -0,0 +1,44 @@ +# distutils: language = c++ +# cython: language_level = 3 + +from IMCtermite cimport cppimctermite + +import json as jn +import decimal + +cdef class imctermite: + + # C++ instance of class => stack allocated (requires nullary constructor!) + cdef cppimctermite cppimc + + # constructor + def __cinit__(self, string rawfile): + self.cppimc = cppimctermite(rawfile) + + # provide raw file + def submit_file(self,string rawfile): + self.cppimc.set_file(rawfile) + + # get JSON list of channels + def get_channels(self, bool data): + chnlst = self.cppimc.get_channels(True,data) + chnlstjn = [jn.loads(chn.decode(errors="ignore")) for chn in chnlst] + return chnlstjn + + # print single channel/all channels + def print_channel(self, string channeluuid, string outputfile, char delimiter): + self.cppimc.print_channel(channeluuid,outputfile,delimiter) + def print_channels(self, string outputdir, char delimiter): + self.cppimc.print_channels(outputdir,delimiter) + + # print table including channels + def print_table(self, string outputfile): + chnlst = self.cppimc.get_channels(True,True) + chnlstjn = [jn.loads(chn.decode(errors="ignore")) for chn in chnlst] + with open(outputfile.decode(),'w') as fout: + for chn in chnlstjn: + fout.write('#' +str(chn['xname']).rjust(19)+str(chn['yname']).rjust(20)+'\n') + fout.write('#'+str(chn['xunit']).rjust(19)+str(chn['yunit']).rjust(20)+'\n') + for n in range(0,len(chn['ydata'])): + fout.write(str(chn['xdata'][n]).rjust(20)+ + str(chn['ydata'][n]).rjust(20)+'\n') diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 0000000..e69de29 diff --git a/python/usage.py b/python/examples/usage.py similarity index 93% rename from python/usage.py rename to python/examples/usage.py index 751b586..e4663dd 100644 --- a/python/usage.py +++ b/python/examples/usage.py @@ -1,11 +1,11 @@ -import imc_termite +import IMCtermite import json import os # declare and initialize instance of "imctermite" by passing a raw-file try : - imcraw = imc_termite.imctermite(b"samples/exampleB.raw") + imcraw = IMCtermite.imctermite(b"samples/exampleB.raw") except RuntimeError as e : raise Exception("failed to load/parse raw-file: " + str(e)) diff --git a/python/usage_adv.py b/python/examples/usage_adv.py similarity index 88% rename from python/usage_adv.py rename to python/examples/usage_adv.py index 1c7414f..9e61e18 100644 --- a/python/usage_adv.py +++ b/python/examples/usage_adv.py @@ -1,5 +1,5 @@ -import imc_termite +import IMCtermite import json import os @@ -15,7 +15,7 @@ for fl in rawlist1: # declare and initialize instance of "imctermite" by passing a raw-file try : - imcraw = imc_termite.imctermite(fl.encode()) + imcraw = IMCtermite.imctermite(fl.encode()) except RuntimeError as e : raise Exception("failed to load/parse raw-file: " + str(e)) @@ -24,7 +24,7 @@ for fl in rawlist1: print(json.dumps(channels,indent=4, sort_keys=False)) # print the channels into a specific directory - imcraw.print_channels(b"./") + imcraw.print_channels(b"./",ord(',')) # print all channels in single file imcraw.print_table(("./"+str(os.path.basename(fl).split('.')[0])+"_allchannels.csv").encode()) diff --git a/python/usage_ext.py b/python/examples/usage_ext.py similarity index 95% rename from python/usage_ext.py rename to python/examples/usage_ext.py index cba97f7..b0e99fa 100644 --- a/python/usage_ext.py +++ b/python/examples/usage_ext.py @@ -1,12 +1,12 @@ -import imc_termite +import IMCtermite import json import os import datetime # declare and initialize instance of "imctermite" by passing a raw-file try : - imcraw = imc_termite.imctermite(b"samples/sampleB.raw") + imcraw = IMCtermite.imctermite(b"samples/sampleB.raw") except RuntimeError as e : raise Exception("failed to load/parse raw-file: " + str(e)) diff --git a/python/examples/usage_files.py b/python/examples/usage_files.py new file mode 100644 index 0000000..26ab209 --- /dev/null +++ b/python/examples/usage_files.py @@ -0,0 +1,29 @@ + +from IMCtermite import imctermite + +def show_results(imcraw) : + + channels = imcraw.get_channels(False) + print(channels) + + channelsData = imcraw.get_channels(True) + print("number of channels: " + str(len(channelsData))) + + for (i,chn) in enumerate(channelsData) : + print(str(i) + " | " + chn['name']) + print(chn['xname'] + " | " + chn['xunit']) + print(chn['xdata'][:10]) + print(chn['yname'] + " | " + chn['yunit']) + print(chn['ydata'][:10]) + + print("") + +# create instance of 'imctermite' +imcraw = imctermite(b'samples/sampleA.raw') + +show_results(imcraw) + +# use previous instance of 'imctermite' to provide new file +imcraw.submit_file(b'samples/sampleB.raw') + +show_results(imcraw) diff --git a/python/makefile b/python/makefile new file mode 100644 index 0000000..fd1ab56 --- /dev/null +++ b/python/makefile @@ -0,0 +1,44 @@ + +GITAG := $(shell git tag -l --sort=version:refname | tail -n1 | sed 's/^v//g') + +setup: + echo $(GITAG) > VERSION + cat ../README.md | grep '^# IMCtermite' -A 50000 > ./README.md + cp -r ../lib ./ + cp -v ../LICENSE ./ + +setup-clean: + rm -vf README.md VERSION LICENSE + rm -rf lib/ + +build: setup + python setup.py build + +build-inplace: setup + python setup.py build_ext --inplace + +build-sdist: setup + python setup.py sdist + +build-bdist: setup + python setup.py bdist + +build-clean: + python setup.py clean --all + rm -vf imctermite*.so imctermite*.cpp + rm -vf IMCtermite*.so IMCtermite*.cpp + rm -rvf dist/ IMCtermite.egg-info/ + +cibuildwheel-build: setup + cibuildwheel --platform linux + +cibuildwheel-clean: + rm -rvf wheelhouse/ + +pypi-upload: + python -m twine upload dist/$(shell ls -t dist/ | head -n1) + +clean: setup build-clean cibuildwheel-clean setup-clean + +run-example: + PYTHONPATH=$(pwd) python examples/usage_files.py diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..89ac503 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = ["setuptools", "wheel","Cython"] +build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +before-all = "" + diff --git a/python/setup.cfg b/python/setup.cfg new file mode 100644 index 0000000..c8ff6e1 --- /dev/null +++ b/python/setup.cfg @@ -0,0 +1,19 @@ + +[metadata] +name = IMCtermite +description = Enables extraction of measurement data from binary files with extension 'raw' used by proprietary software imcFAMOS/imcSTUDIO and facilitates its storage in open source file formats +long_description = file: README.md +version = file: VERSION +author = Record Evolution GmbH +author_email = mario.fink@record-evolution.de +maintainer = Record Evolution GmbH +url= 'https://github.com/RecordEvolution/IMCtermite.git' +license = MIT License +license_files = LICENSE +keywords = IMC, raw, imcFAMOS, imcSTUDIO, imcCRONOS +classifiers = + Programming Language :: Python :: 3, + License :: OSI Approved :: MIT License, + Operating System :: OS Independent + +[options] diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..43250c9 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,6 @@ +from setuptools import setup +from Cython.Build import cythonize + +setup( + ext_modules=cythonize(["IMCtermite.pyx"],language_level=3) +)