From f6761cb6a46c6839a722af9d726f03392294fe5a Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Tue, 14 Feb 2023 00:02:06 -0500 Subject: [PATCH 1/8] rename repo to pylsl --- README.md | 12 ++++++------ setup.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f36e38d..80e5e6a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pylsl -[![Build status](https://ci.appveyor.com/api/projects/status/ggouc09585l2518i/branch/master?svg=true)](https://ci.appveyor.com/project/cboulay/liblsl-python/branch/master) +![publish workflow](https://github.com/labstreaminglayer/pylsl/actions/workflows/publish-to-pypi.yml/badge.svg) [![PyPI version](https://badge.fury.io/py/pylsl.svg)](https://badge.fury.io/py/pylsl) This is the Python interface to the [Lab Streaming Layer (LSL)](https://github.com/sccn/labstreaminglayer). @@ -32,9 +32,9 @@ For several distributions, the pip distribution ships with lsl.dll. For every ot ## Self-built -* Download the pylsl source: `git clone https://github.com/labstreaminglayer/liblsl-Python.git && cd liblsl-Python` -* Copy the shared object (see Prerequisites above) into `liblsl-Python/pylsl/lib`. -* From the `liblsl-Python` working directory, run `pip install .`. +* Download the pylsl source: `git clone https://github.com/labstreaminglayer/pylsl.git && cd pylsl` +* Copy the shared object (see Prerequisites above) into `pylsl/pylsl/lib`. +* From the `pylsl` working directory, run `pip install .`. * Note: You can use `pip install -e .` to install while keeping the files in-place. This is convenient for developing pylsl. # Usage @@ -47,7 +47,7 @@ You can get a list of the examples with `python -c "import pylsl.examples; help( See the note above about separately installing the liblsl dependency on non-Windows platforms. `pylsl` will search for liblsl first in the package directory (default location for Windows), then in normal system library folders, then finally at the filepath specified by an environment variable named `PYLSL_LIB`. A user-installed liblsl will typically be findable by Python's `util.find_library` in most cases. -If `pylsl` cannot find the liblsl binary (e.g., see [this issue](https://github.com/labstreaminglayer/liblsl-Python/issues/48)), set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. i.e., +If `pylsl` cannot find the liblsl binary (e.g., see [this issue](https://github.com/labstreaminglayer/pylsl/issues/48)), set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. i.e., `LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib python -m pylsl.examples.{name-of-example}` @@ -83,7 +83,7 @@ When we did make manylinux distributions, these relied on special liblsl builds # Known Issues with Multithreading on Linux * At least for some versions of pylsl , is has been reported that running on Linux one cannot call ``pylsl`` functions from a thread that is not the main thread. This has been reported to cause access violations, and can occur during pulling from an inlet, and also from accessing an inlets info structure in a thread. -* Recent tests with mulithreading (especially when safeguarding library calls with locks) using Python 3.7.6. with pylsl 1.14 on Linux Mint 20 suggest that this issue is solved, or at least depends on your machine. See https://github.com/labstreaminglayer/liblsl-Python/issues/29 +* Recent tests with mulithreading (especially when safeguarding library calls with locks) using Python 3.7.6. with pylsl 1.14 on Linux Mint 20 suggest that this issue is solved, or at least depends on your machine. See https://github.com/labstreaminglayer/pylsl/issues/29 # Acknowledgments diff --git a/setup.py b/setup.py index 3b743f6..57f00fc 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def get_tag(self): long_description_content_type="text/markdown", # The project's main homepage. - url='https://github.com/labstreaminglayer/liblsl-Python', + url='https://github.com/labstreaminglayer/pylsl', # Author details author='Christian Kothe', From 21fbbc30811fe151992654cdc5fa62a9a0f4d0be Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Tue, 14 Feb 2023 00:07:04 -0500 Subject: [PATCH 2/8] Update maintainer text in README --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 80e5e6a..5588f8e 100644 --- a/README.md +++ b/README.md @@ -55,18 +55,12 @@ If `pylsl` cannot find the liblsl binary (e.g., see [this issue](https://github. ## Continuous Integration -pylsl uses continuous integration and distribution. - -Whenever a new commit is pushed, AppVeyor prepares several files. First it prepares the source wheels -- this is useful on any platform & Python version that does not have a specific binary distribution. Then it prepares the binary wheels; it downloads liblsl from its releases page, copies it to the package, then builds wheels for distribution. This process is repeated for several variants of Windows and Mac. - -In addition, whenever a new `git tag` is used on a commit that is pushed to the master branch, the CI systems will deploy the wheels to pypi. +pylsl uses continuous integration and distribution. GitHub Actions will upload a new release to pypi whenever a Release is created in GitHub. ### Linux Binaries Deprecated We recently stopped building binary wheels for Linux. In practice, the `manylinux` dependencies were often incompatible with real systems. -When we did make manylinux distributions, these relied on special liblsl builds that are not automatically pushed to the liblsl releases page. Special pipelines needed to be run manually on [Azure](https://dev.azure.com/labstreaminglayer/liblsl), then the artifacts uploaded to the release page. The Azure pipelines config remains in the liblsl repo in case it is needed again (unlikely). - ## Manual Distribution 1. Manual way: From 16a4198087936386e866d7239bfde32d1fef6d6b Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Sun, 19 Mar 2023 19:33:36 -0400 Subject: [PATCH 3/8] Update README.md More detailed explanation of liblsl loading; refer to liblsl docs for installation. --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5588f8e..01cce98 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,17 @@ the GitHub project). ## Prerequisites -On all non-Windows platforms and for some Windows-Python combinations, you must first obtain a liblsl shared library: +On all non-Windows platforms and for some Windows-Python combinations, you must first obtain a liblsl shared library. See the [liblsl repo documentation](https://github.com/sccn/liblsl) for further details. -* On many platforms it can be installed with `conda install -c conda-forge liblsl` -* Additionally, on Mac it can be installed with `brew install labstreaminglayer/tap/lsl` -* You might be able to find the appropriate liblsl shared object (*.so on Linux, *.dylib on MacOS, or *.dll on Windows) from the [liblsl release page](https://github.com/sccn/liblsl/releases). -* Otherwise you might try to clone liblsl and use its `standalone_compilation_linux.sh` script (works on raspberry pi). +## Get pylsl from PyPI -## Prepared distributions +* `pip install pylsl` -Install from [pypi](https://pypi.org/project/pylsl/) -using [pip](https://pip.pypa.io/en/stable/installing/): `pip install pylsl` +## Get pylsl from source -For several distributions, the pip distribution ships with lsl.dll. For every other case, liblsl must be installed somewhere on the PATH (see Prerequisites above) or downloaded and copied somewhere on the search path. We recommend you copy it to the pylsl installed module path's `lib` subfolder. i.e. `{path/to/env/}site-packages/pylsl/lib`. Use `python -m site` to find the "site-packages" path. -(use `cp -L` on platforms that use symlinks) - -## Self-built +This should only be necessary if you need to modify or debug pylsl. * Download the pylsl source: `git clone https://github.com/labstreaminglayer/pylsl.git && cd pylsl` -* Copy the shared object (see Prerequisites above) into `pylsl/pylsl/lib`. * From the `pylsl` working directory, run `pip install .`. * Note: You can use `pip install -e .` to install while keeping the files in-place. This is convenient for developing pylsl. @@ -43,13 +35,21 @@ See the examples in pylsl/examples. Note that these can be run directly from the You can get a list of the examples with `python -c "import pylsl.examples; help(pylsl.examples)"` -## liblsl dependency +## liblsl loading + +`pylsl` will search for `liblsl` first at the filepath specified by an environment variable named `PYLSL_LIB`, then in the package directory (default location for Windows), then finally in normal system library folders. + +If the shared object is not installed onto a standard search path (or it is but can't be found for some [other bug](https://github.com/labstreaminglayer/pylsl/issues/48)), then we recommend that you copy it to the pylsl installed module path's `lib` subfolder. i.e. `{path/to/env/}site-packages/pylsl/lib`. -See the note above about separately installing the liblsl dependency on non-Windows platforms. `pylsl` will search for liblsl first in the package directory (default location for Windows), then in normal system library folders, then finally at the filepath specified by an environment variable named `PYLSL_LIB`. A user-installed liblsl will typically be findable by Python's `util.find_library` in most cases. +* The `site-packages/pylsl` path will only exist _after_ you install `pylsl` in your Pyton environment. +* You may have to create the `lib` subfolder. +* Use `python -m site` to find the "site-packages" path. +* Use `cp -L` on platforms that use symlinks. -If `pylsl` cannot find the liblsl binary (e.g., see [this issue](https://github.com/labstreaminglayer/pylsl/issues/48)), set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. i.e., +Alternatively, you can use an the environment variable. Set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. For example, -`LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib python -m pylsl.examples.{name-of-example}` +1. `PYLSL_LIB=/usr/local/lib/liblsl.so python -m pylsl.examples.{name-of-example}`, or +2. `LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib python -m pylsl.examples.{name-of-example}` # For maintainers @@ -76,7 +76,7 @@ We recently stopped building binary wheels for Linux. In practice, the `manylinu # Known Issues with Multithreading on Linux -* At least for some versions of pylsl , is has been reported that running on Linux one cannot call ``pylsl`` functions from a thread that is not the main thread. This has been reported to cause access violations, and can occur during pulling from an inlet, and also from accessing an inlets info structure in a thread. +* At least for some versions of pylsl, is has been reported that running on Linux one cannot call ``pylsl`` functions from a thread that is not the main thread. This has been reported to cause access violations, and can occur during pulling from an inlet, and also from accessing an inlets info structure in a thread. * Recent tests with mulithreading (especially when safeguarding library calls with locks) using Python 3.7.6. with pylsl 1.14 on Linux Mint 20 suggest that this issue is solved, or at least depends on your machine. See https://github.com/labstreaminglayer/pylsl/issues/29 # Acknowledgments From 13853cf17e7e3622c3984356308402956f310d40 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Wed, 4 Oct 2023 08:58:36 -0400 Subject: [PATCH 4/8] Update SendDataAdvanced.py with para about formats --- pylsl/examples/SendDataAdvanced.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pylsl/examples/SendDataAdvanced.py b/pylsl/examples/SendDataAdvanced.py index de50cb4..9ec5e43 100644 --- a/pylsl/examples/SendDataAdvanced.py +++ b/pylsl/examples/SendDataAdvanced.py @@ -27,6 +27,11 @@ def main(name='LSLExampleAmp', stream_type='EEG', srate=100): # First create a new stream info. # The first 4 arguments are stream name, stream type, number of channels, and # sampling rate -- all parameterized by the keyword arguments or the channel list above. + # The 5th parameter is the data format. This should match the origin format (unless the + # data will be transformed prior to pushing, then it should match the transformed-to format). + # Possible values are "float32", "double64", "string", "int32", "int16", "int8", or "int64". + # Alternatively, one could use the constants in the pylsl namespace beginning with `cf_`. + # i.e., cf_float32, cf_double64, etc. # For this example, we will always use float32 data so we provide that as the 5th parameter. # The last value would be the serial number of the device or some other more or # less locally unique identifier for the stream as far as available (you From da6df80d61a03a85507bcd2757ce168ebe5e7812 Mon Sep 17 00:00:00 2001 From: Charles Guan Date: Mon, 16 Oct 2023 15:56:59 -0700 Subject: [PATCH 5/8] Mention preference for numpy arrays, which will not fall-back into the TypeError catch --- pylsl/pylsl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pylsl/pylsl.py b/pylsl/pylsl.py index 4827ce6..a787616 100644 --- a/pylsl/pylsl.py +++ b/pylsl/pylsl.py @@ -458,8 +458,9 @@ def push_sample(self, x, timestamp=0.0, pushthrough=True): def push_chunk(self, x, timestamp=0.0, pushthrough=True): """Push a list of samples into the outlet. - samples -- A list of samples, either as a list of lists or a list of - multiplexed values. + samples -- A list of samples, preferably as a numpy array. `samples` + can also be a list of lists, or a list of multiplexed + values. timestamp -- Optionally the capture time of the most recent sample, in agreement with local_clock(); if omitted, the current time is used. The time stamps of other samples are From c4210f925b20e55a1f2ecde2602949b4316b9c40 Mon Sep 17 00:00:00 2001 From: Charles Guan Date: Mon, 16 Oct 2023 16:41:04 -0700 Subject: [PATCH 6/8] Add timestamp vector option to push_chunk --- pylsl/pylsl.py | 51 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/pylsl/pylsl.py b/pylsl/pylsl.py index a787616..55c76e9 100644 --- a/pylsl/pylsl.py +++ b/pylsl/pylsl.py @@ -411,6 +411,7 @@ def __init__(self, info, chunk_size=0, max_buffered=360): self.channel_count = info.channel_count() self.do_push_sample = fmt2push_sample[self.channel_format] self.do_push_chunk = fmt2push_chunk[self.channel_format] + self.do_push_chunk_n = fmt2push_chunk_n[self.channel_format] self.value_type = fmt2type[self.channel_format] self.sample_type = self.value_type * self.channel_count @@ -458,44 +459,64 @@ def push_sample(self, x, timestamp=0.0, pushthrough=True): def push_chunk(self, x, timestamp=0.0, pushthrough=True): """Push a list of samples into the outlet. - samples -- A list of samples, preferably as a numpy array. `samples` - can also be a list of lists, or a list of multiplexed - values. - timestamp -- Optionally the capture time of the most recent sample, in - agreement with local_clock(); if omitted, the current + samples -- A list of samples, preferably as a 2-D numpy array. + `samples` can also be a list of lists, or a list of + multiplexed values. + timestamp -- Optional, float or 1-D list of floats. + If float: the capture time of the most recent sample, in + agreement with local_clock(); if omitted/default (0.0), the current time is used. The time stamps of other samples are automatically derived according to the sampling rate of - the stream. (default 0.0) + the stream. + If list of floats: the time stamps for each sample. + Must be the same length as `samples`. pushthrough Whether to push the chunk through to the receivers instead of buffering it with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag. (default True) + Note: performance is optimized for the following argument types: + - `samples`: 2-D numpy array + - `timestamp`: float """ + # Convert timestamp to corresponding ctype + try: + timestamp_c = c_double(timestamp) + except TypeError: + try: + timestamp_c = (c_double * len(timestamp))(*timestamp) + except TypeError: + raise TypeError( + "timestamp must be a float or an iterable of floats" + ) + try: n_values = self.channel_count * len(x) data_buff = (self.value_type * n_values).from_buffer(x) handle_error(self.do_push_chunk(self.obj, data_buff, c_long(n_values), - c_double(timestamp), + timestamp_c, c_int(pushthrough))) except TypeError: + # don't send empty chunks if len(x): if type(x[0]) is list: x = [v for sample in x for v in sample] if self.channel_format == cf_string: x = [v.encode('utf-8') for v in x] if len(x) % self.channel_count == 0: + # x is a flattened list of multiplexed values constructor = self.value_type * len(x) # noinspection PyCallingNonCallable handle_error(self.do_push_chunk(self.obj, constructor(*x), c_long(len(x)), - c_double(timestamp), + timestamp_c, c_int(pushthrough))) else: raise ValueError("Each sample must have the same number of channels (" + str(self.channel_count) + ").") + def have_consumers(self): """Check whether consumers are currently registered. @@ -1402,11 +1423,15 @@ def find_liblsl_libraries(verbose=False): push_sample_int64 = lib.lsl_push_sample_ltp pull_sample_int64 = lib.lsl_pull_sample_l push_chunk_int64 = lib.lsl_push_chunk_ltp + push_chunk_int64_n = lib.lsl_push_chunk_ltnp pull_chunk_int64 = lib.lsl_pull_chunk_l else: def push_sample_int64(*_): raise NotImplementedError('int64 support isn\'t enabled on your platform') - pull_sample_int64 = push_chunk_int64 = pull_chunk_int64 = push_sample_int64 + pull_sample_int64 = push_sample_int64 + push_chunk_int64 = push_sample_int64 + push_chunk_int64_n = push_sample_int64 + pull_chunk_int64 = push_sample_int64 # set up some type maps string2fmt = {'float32': cf_float32, 'double64': cf_double64, @@ -1426,10 +1451,14 @@ def push_sample_int64(*_): fmt2push_chunk = [[], lib.lsl_push_chunk_ftp, lib.lsl_push_chunk_dtp, lib.lsl_push_chunk_strtp, lib.lsl_push_chunk_itp, lib.lsl_push_chunk_stp, lib.lsl_push_chunk_ctp, push_chunk_int64] + fmt2push_chunk_n = [[], lib.lsl_push_chunk_ftnp, lib.lsl_push_chunk_dtnp, + lib.lsl_push_chunk_strtnp, lib.lsl_push_chunk_itnp, + lib.lsl_push_chunk_stnp, lib.lsl_push_chunk_ctnp, push_chunk_int64_n] fmt2pull_chunk = [[], lib.lsl_pull_chunk_f, lib.lsl_pull_chunk_d, lib.lsl_pull_chunk_str, lib.lsl_pull_chunk_i, lib.lsl_pull_chunk_s, lib.lsl_pull_chunk_c, pull_chunk_int64] except: # if not available - fmt2push_chunk = [None, None, None, None, None, None, None, None] - fmt2pull_chunk = [None, None, None, None, None, None, None, None] + fmt2push_chunk = [None] * len(fmt2string) + fmt2push_chunk_n = [None] * len(fmt2string) + fmt2pull_chunk = [None] * len(fmt2string) From d3b89c1ad80e4e6cd9a0fb7aa65d09de732fb832 Mon Sep 17 00:00:00 2001 From: Charles Guan Date: Mon, 16 Oct 2023 16:50:03 -0700 Subject: [PATCH 7/8] Call the appropriate push_chunk function --- pylsl/pylsl.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/pylsl/pylsl.py b/pylsl/pylsl.py index 55c76e9..13a75eb 100644 --- a/pylsl/pylsl.py +++ b/pylsl/pylsl.py @@ -482,9 +482,12 @@ def push_chunk(self, x, timestamp=0.0, pushthrough=True): # Convert timestamp to corresponding ctype try: timestamp_c = c_double(timestamp) + # Select the corresponding push_chunk method + liblsl_push_chunk_func = self.do_push_chunk except TypeError: try: timestamp_c = (c_double * len(timestamp))(*timestamp) + liblsl_push_chunk_func = self.do_push_chunk_n except TypeError: raise TypeError( "timestamp must be a float or an iterable of floats" @@ -493,10 +496,14 @@ def push_chunk(self, x, timestamp=0.0, pushthrough=True): try: n_values = self.channel_count * len(x) data_buff = (self.value_type * n_values).from_buffer(x) - handle_error(self.do_push_chunk(self.obj, data_buff, - c_long(n_values), - timestamp_c, - c_int(pushthrough))) + handle_error( + liblsl_push_chunk_func( + self.obj, data_buff, + c_long(n_values), + timestamp_c, + c_int(pushthrough) + ) + ) except TypeError: # don't send empty chunks if len(x): @@ -508,10 +515,14 @@ def push_chunk(self, x, timestamp=0.0, pushthrough=True): # x is a flattened list of multiplexed values constructor = self.value_type * len(x) # noinspection PyCallingNonCallable - handle_error(self.do_push_chunk(self.obj, constructor(*x), - c_long(len(x)), - timestamp_c, - c_int(pushthrough))) + handle_error( + liblsl_push_chunk_func( + self.obj, constructor(*x), + c_long(len(x)), + timestamp_c, + c_int(pushthrough) + ) + ) else: raise ValueError("Each sample must have the same number of channels (" + str(self.channel_count) + ").") From cbddf03f3e6b644762b016d66d9347b4a3169865 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Wed, 25 Oct 2023 15:54:07 -0400 Subject: [PATCH 8/8] Bump version to 1.16.2, includes liblsl dependency bump. --- .github/workflows/publish-to-pypi.yml | 4 ++-- README.md | 6 ++++-- pylsl/version.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 57add4e..ea79ca7 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -5,8 +5,8 @@ on: types: [published] env: - LSL_RELEASE_URL: 'https://github.com/sccn/liblsl/releases/download/v1.16.1/' - LSL_RELEASE: '1.16.1' + LSL_RELEASE_URL: 'https://github.com/sccn/liblsl/releases/download/v1.16.2/' + LSL_RELEASE: '1.16.2' defaults: run: diff --git a/README.md b/README.md index 01cce98..e9ee932 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ If the shared object is not installed onto a standard search path (or it is but * Use `python -m site` to find the "site-packages" path. * Use `cp -L` on platforms that use symlinks. -Alternatively, you can use an the environment variable. Set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. For example, +Alternatively, you can use an environment variable. Set the `PYLSL_LIB` environment variable to the location of the library or set `LD_LIBRARY_PATH` to the folder containing the library. For example, 1. `PYLSL_LIB=/usr/local/lib/liblsl.so python -m pylsl.examples.{name-of-example}`, or 2. `LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib python -m pylsl.examples.{name-of-example}` @@ -56,6 +56,8 @@ Alternatively, you can use an the environment variable. Set the `PYLSL_LIB` envi ## Continuous Integration pylsl uses continuous integration and distribution. GitHub Actions will upload a new release to pypi whenever a Release is created in GitHub. +Before creating the GitHub release, be sure to bump the version number in `pylsl/version.py` and consider updating the liblsl dependency +in `.github/workflows/publish-to-pypi.yml`. ### Linux Binaries Deprecated @@ -76,7 +78,7 @@ We recently stopped building binary wheels for Linux. In practice, the `manylinu # Known Issues with Multithreading on Linux -* At least for some versions of pylsl, is has been reported that running on Linux one cannot call ``pylsl`` functions from a thread that is not the main thread. This has been reported to cause access violations, and can occur during pulling from an inlet, and also from accessing an inlets info structure in a thread. +* At least for some versions of pylsl, it has been reported that running on Linux one cannot call ``pylsl`` functions from a thread that is not the main thread. This has been reported to cause access violations, and can occur during pulling from an inlet, and also from accessing an inlets info structure in a thread. * Recent tests with mulithreading (especially when safeguarding library calls with locks) using Python 3.7.6. with pylsl 1.14 on Linux Mint 20 suggest that this issue is solved, or at least depends on your machine. See https://github.com/labstreaminglayer/pylsl/issues/29 # Acknowledgments diff --git a/pylsl/version.py b/pylsl/version.py index 4b2887f..e4a8b8f 100644 --- a/pylsl/version.py +++ b/pylsl/version.py @@ -1 +1 @@ -__version__ = '1.16.1' +__version__ = '1.16.2'