diff --git a/.bonsai/Bonsai.config b/.bonsai/Bonsai.config new file mode 100644 index 0000000..6abedec --- /dev/null +++ b/.bonsai/Bonsai.config @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.bonsai/NuGet.config b/.bonsai/NuGet.config new file mode 100644 index 0000000..aa5beec --- /dev/null +++ b/.bonsai/NuGet.config @@ -0,0 +1,5 @@ + + + + + diff --git a/.bonsai/Setup.cmd b/.bonsai/Setup.cmd new file mode 100644 index 0000000..92d983d --- /dev/null +++ b/.bonsai/Setup.cmd @@ -0,0 +1,4 @@ +@echo off +pushd %~dp0 +powershell -ExecutionPolicy Bypass -File ./Setup.ps1 +popd \ No newline at end of file diff --git a/.bonsai/Setup.ps1 b/.bonsai/Setup.ps1 new file mode 100644 index 0000000..890eeda --- /dev/null +++ b/.bonsai/Setup.ps1 @@ -0,0 +1,20 @@ +Push-Location $PSScriptRoot +if (!(Test-Path "./Bonsai.exe")) { + $release = "https://github.com/bonsai-rx/bonsai/releases/latest/download/Bonsai.zip" + $configPath = "./Bonsai.config" + if (Test-Path $configPath) { + [xml]$config = Get-Content $configPath + $bootstrapper = $config.PackageConfiguration.Packages.Package.where{$_.id -eq 'Bonsai'} + if ($bootstrapper) { + $version = $bootstrapper.version + $release = "https://github.com/bonsai-rx/bonsai/releases/download/$version/Bonsai.zip" + } + } + Invoke-WebRequest $release -OutFile "temp.zip" + Move-Item -Path "NuGet.config" "temp.config" -ErrorAction SilentlyContinue + Expand-Archive "temp.zip" -DestinationPath "." -Force + Move-Item -Path "temp.config" "NuGet.config" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "temp.zip" +} +& .\Bonsai.exe --no-editor +Pop-Location \ No newline at end of file diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..a452a7e --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "bonsai.sgen": { + "version": "0.7.2", + "commands": [ + "bonsai.sgen" + ], + "rollForward": false + }, + "docfx": { + "version": "2.78.3", + "commands": [ + "docfx" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0595f32 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,46 @@ +root = true + +[*] +indent_style = space + +#-------------------------------------------------------------------------------------------------- +# XML, JSON, and web files +#-------------------------------------------------------------------------------------------------- +[*.{xml,csproj,vcxproj,vcxproj.filters,shproj,props,targets,config,nuspec,resx,vsixmanifest,wxs,vstemplate,slnx}] +indent_size = 2 + +[*.json] +indent_size = 2 + +[*.{html,css}] +indent_size = 2 + +#-------------------------------------------------------------------------------------------------- +# C++ +#-------------------------------------------------------------------------------------------------- +[*.{c,cpp,h,hpp,ixx}] +indent_size = 4 +charset = utf-8-bom +trim_trailing_whitespace = true +insert_final_newline = true + +#-------------------------------------------------------------------------------------------------- +# C# +#-------------------------------------------------------------------------------------------------- +[*.{cs,csx}] +indent_size = 4 +charset = utf-8-bom +trim_trailing_whitespace = true +insert_final_newline = true + +# Language keyword vs full type name +# Do not create a message because explicitly sized types can be convenient in interop where bit size matters +dotnet_style_predefined_type_for_locals_parameters_members = true:none +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# Suppress warnings about not using modern collection syntax +dotnet_style_prefer_collection_expression = never +csharp_style_prefer_range_operator = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..601a4e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,43 @@ +# Auto detect text files and normalize line endings +* text=auto + +# Scripts +*.cmd text eol=crlf +*.ps1 text + +# Config +*.gitignore text +*.gitattributes text +*.gitmodules text eol=lf +*.editorconfig text +*.git-blame-ignore-revs text +*.sln text +*.proj text +*.props text +*.targets text +*.csproj text +*.wixproj text +*.config text +*.json text +*.xml text +*.yml text + +# Code +*.manifest text +*.vsixmanifest text +*.vstemplate text +*.resx text +*.cs text +*.bonsai text +*.wxs text + +# Documents +LICENSE text +*.md text diff=markdown +*.rtf diff=astextplain + +# Graphics +*.png binary +*.ico binary +*.gif binary +*.svg text diff --git a/.github/workflows/UclOpen.Acquisition.yml b/.github/workflows/UclOpen.Acquisition.yml new file mode 100644 index 0000000..80c5305 --- /dev/null +++ b/.github/workflows/UclOpen.Acquisition.yml @@ -0,0 +1,348 @@ +# ======================================================================================================================================================================= +# UclOpen.Acquisition CI/CD +# ======================================================================================================================================================================= +# Index: +# * Build, test, and package .NET +# * Build documentation +# * Render workflow images +# * Publish packages to GitHub +# * Publish packages to NuGet.org +# * Publish documentation +# ======================================================================================================================================================================= +# Note that this is a generic workflow meant for all Bonsai packages. Minor local modifications are fine, see https://github.com/bonsai-rx/prefect for more information. +# ======================================================================================================================================================================= +name: UclOpen.Acquisition +on: + push: + # This prevents tag pushes from triggering this workflow + branches: ['**'] + pull_request: + release: + types: [published] + workflow_dispatch: + inputs: + publish-documentation: + description: "Publish documentation to GitHub Pages?" + default: "false" +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + ContinuousIntegrationBuild: true +jobs: + # ===================================================================================================================================================================== + # Build, test, and package .NET + # ___ _ _ _ _ _ _ _ _ _ ___ _____ + # | _ )_ _(_) |__| | | |_ ___ __| |_ __ _ _ _ __| | _ __ __ _ __| |____ _ __ _ ___ | \| | __|_ _| + # | _ \ || | | / _` |_ | _/ -_|_-< _|_ / _` | ' \/ _` | | '_ \/ _` / _| / / _` / _` / -_) _| .` | _| | | + # |___/\_,_|_|_\__,_( ) \__\___/__/\__( ) \__,_|_||_\__,_| | .__/\__,_\__|_\_\__,_\__, \___| (_)_|\_|___| |_| + # |/ |/ |_| |___/ + # ===================================================================================================================================================================== + build: + strategy: + fail-fast: false + matrix: + platform: + - name: Windows x64 + os: windows-latest + rid: win-x64 + - name: Linux x64 + os: ubuntu-22.04 + rid: linux-x64 + configuration: ['debug', 'release'] + include: + - platform: + rid: win-x64 + configuration: release + collect-packages: true + name: ${{matrix.platform.name}} ${{matrix.configuration}} + runs-on: ${{matrix.platform.os}} + outputs: + need-workflow-image-render: ${{steps.configure-build.outputs.need-workflow-image-render}} + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + # ----------------------------------------------------------------------- Set up tools + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + # ----------------------------------------------------------------------- Configure build + - name: Configure build + id: configure-build + uses: bonsai-rx/configure-build@v1 + + # ----------------------------------------------------------------------- Build + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration ${{matrix.configuration}} + + # ----------------------------------------------------------------------- Pack + - name: Pack + id: pack + run: dotnet pack --no-restore --no-build --configuration ${{matrix.configuration}} + + # ----------------------------------------------------------------------- Test + - name: Test .NET Framework 4.7.2 + run: dotnet test --no-restore --no-build --configuration ${{matrix.configuration}} --verbosity normal --framework net472 + - name: Test .NET 8 + run: dotnet test --no-restore --no-build --configuration ${{matrix.configuration}} --verbosity normal --framework net8.0 + - name: Test .NET 8 Windows + if: matrix.platform.rid == 'win-x64' + run: dotnet test --no-restore --no-build --configuration ${{matrix.configuration}} --verbosity normal --framework net8.0-windows + + # ----------------------------------------------------------------------- Collect artifacts + - name: Collect NuGet packages + uses: actions/upload-artifact@v4 + if: matrix.collect-packages && steps.pack.outcome == 'success' && always() + with: + name: Packages + if-no-files-found: error + path: artifacts/package/${{matrix.configuration}}/** + + # ===================================================================================================================================================================== + # Build documentation + # ___ _ _ _ _ _ _ _ + # | _ )_ _(_) |__| | __| |___ __ _ _ _ __ ___ _ _| |_ __ _| |_(_)___ _ _ + # | _ \ || | | / _` | / _` / _ \/ _| || | ' \/ -_) ' \ _/ _` | _| / _ \ ' \ + # |___/\_,_|_|_\__,_| \__,_\___/\__|\_,_|_|_|_\___|_||_\__\__,_|\__|_\___/_||_| + # ===================================================================================================================================================================== + build-documentation: + name: Build documentation + runs-on: ubuntu-latest + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + # ----------------------------------------------------------------------- Set up tools + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Set up .NET tools + run: dotnet tool restore + + # ----------------------------------------------------------------------- Restore + - name: Restore + run: dotnet restore + + # ----------------------------------------------------------------------- Build metadata + - name: Build metadata + id: build-metadata + run: dotnet docfx metadata docs/docfx.json --noRestore + + # ----------------------------------------------------------------------- Build documentation + - name: Build documentation + id: build-documentation + run: dotnet docfx build docs/docfx.json + + # ----------------------------------------------------------------------- Collect artifacts + - name: Collect documentation metadata + uses: actions/upload-artifact@v4 + if: steps.build-metadata.outcome == 'success' && always() + with: + name: DocumentationMetadata + if-no-files-found: error + path: artifacts/docs/api/ + + - name: Collect documentation artifact + uses: actions/upload-artifact@v4 + if: steps.build-documentation.outcome == 'success' && always() + with: + name: DocumentationWebsite + if-no-files-found: error + path: artifacts/docs/site/ + + # ===================================================================================================================================================================== + # Render workflow images + # ___ _ _ __ _ _ + # | _ \___ _ _ __| |___ _ _ __ __ _____ _ _| |__/ _| |_____ __ __ (_)_ __ __ _ __ _ ___ ___ + # | / -_) ' \/ _` / -_) '_| \ V V / _ \ '_| / / _| / _ \ V V / | | ' \/ _` / _` / -_|_-< + # |_|_\___|_||_\__,_\___|_| \_/\_/\___/_| |_\_\_| |_\___/\_/\_/ |_|_|_|_\__,_\__, \___/__/ + # |___/ + # ===================================================================================================================================================================== + workflow-images: + name: Render workflow images + runs-on: windows-latest + needs: build + if: needs.build.outputs.need-workflow-image-render == 'true' + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + # ----------------------------------------------------------------------- Download built packages + - name: Download packages for rendering + uses: actions/download-artifact@v4 + with: + name: Packages + path: artifacts/packages/ + + # ----------------------------------------------------------------------- Set up Bonsai environments + - name: Set up Bonsai environments + uses: bonsai-rx/setup-bonsai@v1 + with: + environment-paths: '**/.bonsai/' + inject-packages: artifacts/packages/*.nupkg + + # ----------------------------------------------------------------------- Render + - name: Render images + id: render + run: pwsh ./docs/export-images.ps1 -OutputFolder artifacts/docs/site/ -Verbose + + # ----------------------------------------------------------------------- Collect artifacts + - name: Collect images + uses: actions/upload-artifact@v4 + if: steps.render.outcome == 'success' && always() + with: + name: DocumentationWorkflowImages + if-no-files-found: error + path: artifacts/docs/site/ + + # ===================================================================================================================================================================== + # Publish packages to GitHub + # ___ _ _ _ _ _ _ ___ _ _ _ _ _ + # | _ \_ _| |__| (_)__| |_ _ __ __ _ __| |____ _ __ _ ___ ___ | |_ ___ / __(_) |_| || |_ _| |__ + # | _/ || | '_ \ | (_-< ' \ | '_ \/ _` / _| / / _` / _` / -_|_-< | _/ _ \ | (_ | | _| __ | || | '_ \ + # |_| \_,_|_.__/_|_/__/_||_| | .__/\__,_\__|_\_\__,_\__, \___/__/ \__\___/ \___|_|\__|_||_|\_,_|_.__/ + # |_| |___/ + # ===================================================================================================================================================================== + publish-github: + name: Publish packages to GitHub + runs-on: ubuntu-latest + needs: build + permissions: + # Needed to attach files to releases + contents: write + # Needed to upload to GitHub Packages + packages: write + if: github.event_name == 'push' || github.event_name == 'release' + steps: + # ----------------------------------------------------------------------- Set up .NET + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + # ----------------------------------------------------------------------- Download built packages + - name: Download built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: artifacts/packages/ + + # ----------------------------------------------------------------------- Upload release assets + - name: Upload release assets + if: github.event_name == 'release' + run: gh release upload --repo ${{github.repository}} ${{github.event.release.tag_name}} artifacts/packages/* --clobber + env: + GH_TOKEN: ${{github.token}} + + # ----------------------------------------------------------------------- Push to GitHub Packages + - name: Push to GitHub Packages + run: dotnet nuget push "artifacts/packages/*.nupkg" --skip-duplicate --no-symbols --api-key ${{secrets.GITHUB_TOKEN}} --source https://nuget.pkg.github.com/${{github.repository_owner}} + env: + # This is a workaround for https://github.com/NuGet/Home/issues/9775 + DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER: 0 + + # ===================================================================================================================================================================== + # Publish packages to NuGet.org + # ___ _ _ _ _ _ _ _ _ ___ _ + # | _ \_ _| |__| (_)__| |_ _ __ __ _ __| |____ _ __ _ ___ ___ | |_ ___ | \| |_ _ / __|___| |_ ___ _ _ __ _ + # | _/ || | '_ \ | (_-< ' \ | '_ \/ _` / _| / / _` / _` / -_|_-< | _/ _ \ | .` | || | (_ / -_) _|_/ _ \ '_/ _` | + # |_| \_,_|_.__/_|_/__/_||_| | .__/\__,_\__|_\_\__,_\__, \___/__/ \__\___/ |_|\_|\_,_|\___\___|\__(_)___/_| \__, | + # |_| |___/ |___/ + # ===================================================================================================================================================================== + publish-packages-nuget-org: + name: Publish packages to NuGet.org + runs-on: ubuntu-latest + environment: public-release + needs: build + if: github.event_name == 'release' + steps: + # ----------------------------------------------------------------------- Set up .NET + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + # ----------------------------------------------------------------------- Download built packages + - name: Download built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: artifacts/packages/ + + # ----------------------------------------------------------------------- Push to NuGet.org + - name: Push to NuGet.org + run: dotnet nuget push "artifacts/packages/*.nupkg" --api-key ${{secrets.NUGET_API_KEY}} --source ${{vars.NUGET_API_URL}} + env: + # This is a workaround for https://github.com/NuGet/Home/issues/9775 + DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER: 0 + + + # ===================================================================================================================================================================== + # Publish documentation + # ___ _ _ _ _ _ _ _ _ + # | _ \_ _| |__| (_)__| |_ __| |___ __ _ _ _ __ ___ _ _| |_ __ _| |_(_)___ _ _ + # | _/ || | '_ \ | (_-< ' \ / _` / _ \/ _| || | ' \/ -_) ' \ _/ _` | _| / _ \ ' \ + # |_| \_,_|_.__/_|_/__/_||_| \__,_\___/\__|\_,_|_|_|_\___|_||_\__\__,_|\__|_\___/_||_| + # ===================================================================================================================================================================== + publish-documentation: + name: Publish documentation + runs-on: ubuntu-latest + # Publishing is not strictly necessary here, but if we're going to do a public release we want to wait to publish the docs until it goes out + needs: [build-documentation, workflow-images, publish-packages-nuget-org] + permissions: + # Both required by actions/deploy-pages + pages: write + id-token: write + environment: + # Intentionally not using the "default" github-pages environment as it's not compatible with this workflow + name: documentation-website + url: ${{steps.publish.outputs.page_url}} + # Only run if the workflow isn't dying and build-documentation was successful and either A) we're releasing or B) we have continuous deployment enabled + if: | + !cancelled() && !failure() && needs.build-documentation.result == 'success' + && (github.event_name == 'release' + || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-documentation == 'true') + || (vars.CONTINUOUS_DOCUMENTATION && github.event_name != 'pull_request') + ) + steps: + # ----------------------------------------------------------------------- Download documentation website components + # It is intentional that we use two independent download steps here as it ensures that workflow images are permitted + # to overwrite any conflicts in the docfx output but not the other way around. + - name: Download documentation website + uses: actions/download-artifact@v4 + with: + name: DocumentationWebsite + + - name: Download workflow images + if: ${{needs.workflow-images.result == 'success'}} + uses: actions/download-artifact@v4 + with: + name: DocumentationWorkflowImages + + # ----------------------------------------------------------------------- Collect artifacts + - name: Upload final documentation website artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '.' + + # ----------------------------------------------------------------------- Publish to GitHub Pages + - name: Publish to GitHub Pages + id: publish + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 791503c..b075f2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -*.editor -*.layout +.vs/ +**/.bonsai/Bonsai.exe* +**/.bonsai/Packages/ **/.bonsai/Settings/ -**/obj/ -**/bin/ \ No newline at end of file +/artifacts/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cad3e2e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/bonsai-docfx"] + path = docs/bonsai-docfx + url = https://github.com/bonsai-rx/docfx-tools diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..91586df --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..b7ac253 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..31a5712 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright 2026 (c) University College London and Contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 239720e..0000000 --- a/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Bonsai-Workflows - -This repository contains Bonsai workflows organized into three main categories: - -## Repository Structure - -### 📋 [Protocols](./protocols/) -Workflows specific to individual behavioural protocols, or general protocol structures used in the lab. Examples include AFC tasks, passive stimulus presentations, etc. - -### 🔧 [Calibrations](./calibrations/) -Standalone calibration workflows such as gamma-calibration, laser-power-calibration, etc. - -### ⚙️ [Utilities](./utilities/) -Workflow "modules" that interface with hardware components. Examples include camera acquisition, reward delivery, etc. - -## General Guidelines - -When adding new workflows to any category: - -1. **Create a folder** with a descriptive name -2. **Add a README.md** file in the folder that includes: - - Screenshot of the workflow - - Clear description and purpose - - Hardware requirements (if applicable) - - Usage instructions - - Output file specifications -3. **Include the workflow file(s)** (.bonsai files) -4. **Add any configuration files** needed -5. **Test the workflow** with actual hardware before committing diff --git a/UclOpen.Acquisition.sln b/UclOpen.Acquisition.sln new file mode 100644 index 0000000..fd4b790 --- /dev/null +++ b/UclOpen.Acquisition.sln @@ -0,0 +1,58 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32825.248 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UclOpen.Logging", "src\UclOpen.Logging\UclOpen.Logging.csproj", "{29736174-FC8D-4DC6-8382-1996527464C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UclOpen.Devices", "src\UclOpen.Devices\UclOpen.Devices.csproj", "{CB2A0CE5-0362-80B2-E81A-75F40147381C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UclOpen.Core", "src\UclOpen.Core\UclOpen.Core.csproj", "{545CC42C-CC08-0617-E127-A4F018241854}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UclOpen.Video", "src\UclOpen.Video\UclOpen.Video.csproj", "{742346D0-82F2-CEF3-C93F-FC2D6BDA7E11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UclOpen.Vision", "src\UclOpen.Vision\UclOpen.Vision.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{DEE5DD87-39C1-BF34-B639-A387DCCF972B}" + ProjectSection(SolutionItems) = preProject + build\Common.csproj.props = build\Common.csproj.props + build\Common.csproj.targets = build\Common.csproj.targets + build\Common.Tests.csproj.props = build\Common.Tests.csproj.props + build\icon.png = build\icon.png + build\Package.props = build\Package.props + build\Project.csproj.props = build\Project.csproj.props + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29736174-FC8D-4DC6-8382-1996527464C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29736174-FC8D-4DC6-8382-1996527464C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29736174-FC8D-4DC6-8382-1996527464C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29736174-FC8D-4DC6-8382-1996527464C6}.Release|Any CPU.Build.0 = Release|Any CPU + {CB2A0CE5-0362-80B2-E81A-75F40147381C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB2A0CE5-0362-80B2-E81A-75F40147381C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB2A0CE5-0362-80B2-E81A-75F40147381C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB2A0CE5-0362-80B2-E81A-75F40147381C}.Release|Any CPU.Build.0 = Release|Any CPU + {545CC42C-CC08-0617-E127-A4F018241854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {545CC42C-CC08-0617-E127-A4F018241854}.Debug|Any CPU.Build.0 = Debug|Any CPU + {545CC42C-CC08-0617-E127-A4F018241854}.Release|Any CPU.ActiveCfg = Release|Any CPU + {545CC42C-CC08-0617-E127-A4F018241854}.Release|Any CPU.Build.0 = Release|Any CPU + {742346D0-82F2-CEF3-C93F-FC2D6BDA7E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {742346D0-82F2-CEF3-C93F-FC2D6BDA7E11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {742346D0-82F2-CEF3-C93F-FC2D6BDA7E11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {742346D0-82F2-CEF3-C93F-FC2D6BDA7E11}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7CD86964-68EE-43C3-97EC-728837065DDA} + EndGlobalSection +EndGlobal diff --git a/build/Common.Tests.csproj.props b/build/Common.Tests.csproj.props new file mode 100644 index 0000000..c42b25f --- /dev/null +++ b/build/Common.Tests.csproj.props @@ -0,0 +1,6 @@ + + + false + false + + \ No newline at end of file diff --git a/build/Common.csproj.props b/build/Common.csproj.props new file mode 100644 index 0000000..1099119 --- /dev/null +++ b/build/Common.csproj.props @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + + + + + 12.0 + true + strict + enable + true + + + true + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../artifacts')) + + + $(ArtifactsPath)/wsl + + + + + true + Dependency;BonsaiLibrary + + icon.png + $(MSBuildThisFileDirectory)icon.png + + LICENSE + $(MSBuildThisFileDirectory)../LICENSE + + README.md + $(MSBuildThisFileDirectory)../docs/README.md + $(MSBuildThisFileDirectory)README.nuget.md + $(MSBuildProjectDirectory)\README.md + $(MSBuildProjectDirectory)\README.nuget.md + + + false + true + snupkg + + + false + true + + + + + $(WarningsAsErrors);NU1701;CS7035 + + + true + + + true + + + $(MSBuildThisFileDirectory)../.bonsai/Bonsai.exe + + + + + + \ No newline at end of file diff --git a/build/Common.csproj.targets b/build/Common.csproj.targets new file mode 100644 index 0000000..07f4916 --- /dev/null +++ b/build/Common.csproj.targets @@ -0,0 +1,46 @@ + + + + + + + + + + + $(TargetName.ToLowerInvariant()) + DotnetTool + + + 1591,1573 + + + + + + + + 0 + 42.42.42-dev$(DevVersion) + + $(CiBuildVersion) + + + + + + + + + + + + \ No newline at end of file diff --git a/build/Package.props b/build/Package.props new file mode 100644 index 0000000..a75e8ef --- /dev/null +++ b/build/Package.props @@ -0,0 +1,9 @@ + + + Bonsai Rx UclOpen + https://ucl-open.github.io/acquisition + + University College London + Copyright © University College London and Contributors + + \ No newline at end of file diff --git a/build/Project.csproj.props b/build/Project.csproj.props new file mode 100644 index 0000000..1d004b4 --- /dev/null +++ b/build/Project.csproj.props @@ -0,0 +1,5 @@ + + + annotations + + \ No newline at end of file diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..e845695 Binary files /dev/null and b/build/icon.png differ diff --git a/calibrations/Audio-Visual lab calibration workflow/README.md b/calibrations/Audio-Visual lab calibration workflow/README.md deleted file mode 100644 index 9ca0472..0000000 --- a/calibrations/Audio-Visual lab calibration workflow/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Audio-Visual lag calibration workflow - -### Purpose -This workflow is used to measure the response time of the Auditory and Visual stimuli once the command is send by the computer. The measured relative lag between those is then compensated while running Experiments. - -## Hardware Requirements -- Projector (or Monitor) -- Photodiode (for visual stimuli) -- Exp. controll board (to threshold Photodiode) -- Speakers -- Microphone (or alt. feeding speaker signal directly into DAQ) -- Harp.Behaviour board or NI-DAQ - -## Bonsai Workflow -- TBD - -## Properties -Setting up - -- position photodiode and set threshold that reliably switched of On/Off of visual stimuli -- position microphone or alt. wire speaker signal to DAQ - -Inputs - - -Outputs - -- Response lag for Auditory and Visual stimuli -- relative difference between both in a cenvenien format (calibration csv file) to be loaded by the setup while running diff --git a/calibrations/Gamma-correction workflow/README.md b/calibrations/Gamma-correction workflow/README.md deleted file mode 100644 index e80bde9..0000000 --- a/calibrations/Gamma-correction workflow/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Gamma-correction workflow - -### Purpose -This workflow is used to perform the gamma-correction of the monitor to linearize the light output of a projector (or monitor). - -## Hardware Requirements -- Projector or monitor -- Harp.Behaviour board -- Photometer (or powermeter unclear to me with different wavelengths?) - -## Bonsai Workflow -- TBD - -## Properties - -Inputs - -- Reading from photometer -- Brightness levels to display - -Outputs - -- Brightness readings -- Fittet (sline interp or similar) gamma-correction curve - -Properties - -- Should run automaticall through all settings once photometer is placed infront of projector. \ No newline at end of file diff --git a/calibrations/README.md b/calibrations/README.md deleted file mode 100644 index d895bc0..0000000 --- a/calibrations/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Calibrations - -Workflows that are standalone calibrations etc. like "gamma-calibration" or "laser-power-calibration". - -## Adding New Calibrations - -To add a new calibration to this repository: - -1. **Create a folder** with a descriptive name (e.g., `gamma-calibration`, `laser-power-calibration`) -2. **Add a README.md** file in the folder that includes: - - Screenshot of the workflow - - Description of what is being calibrated - - Hardware requirements and connections - - Step-by-step usage instructions - - Output file specifications -3. **Include the workflow file(s)** (.bonsai files) -4. **Add any configuration files** needed for the calibration -5. **Test the calibration** with actual hardware before committing diff --git a/calibrations/Voltage-to-Galvo-Position/README.md b/calibrations/Voltage-to-Galvo-Position/README.md deleted file mode 100644 index 2a2db4f..0000000 --- a/calibrations/Voltage-to-Galvo-Position/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Voltage to Galvo Position - -### Purpose -The purpose of this workflow is to calibrate galvonometric mirror movement with voltage inputs controlled by Bonsai with real-time feedback to be used in wider experimental workflows. - -## Hardware Requirements -- Galvonometric mirrors -- Data Acquisition Board -- Camera - -## Bonsai Workflow - -## Properties - -Inputs - -- Camera data stream -- Voltage commands -- Transform function for pixels to mm and volt to pixel conversions -- Mouse position - -Outputs - -- Voltage outputs changes from calibration -- File with coordinate mapping conversions - -Properties - -- ROI mapping -- Coordinates in FOV -- Range of movement/voltages for galvos -- Sampling rate \ No newline at end of file diff --git a/calibrations/Voltage-to-Laser-Power/README.md b/calibrations/Voltage-to-Laser-Power/README.md deleted file mode 100644 index 96d23fd..0000000 --- a/calibrations/Voltage-to-Laser-Power/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Voltage to Laser Power - -### Purpose -The purpose of this workflow is to calibrate laser power with voltage inputs controlled by Bonsai with real-time feedback to be used in wider experimental workflows. - -## Hardware Requirements -- Laser -- Data Acquisition Board -- Power Meter - -## Bonsai Workflow - -## Properties - -Inputs - -- Laser power from power reading -- Voltage commands -- ExpressionTransform or other transform for conversions and scaling of voltage inputs for desired power outputs - - -Outputs - -- Voltage outputs for laser power from calibration -- File with voltage inputs and corresponding power inputs - -Properties - -- Wavelength of laser -- Sampling rate -- Min and max voltages -- Power range - -- Could be automated to run through multiple voltages and record corresponding power outputs in real time? \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..36e5210 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,118 @@ +# acquisition + +This repository provides a toolkit of Bonsai-based workflows and operators for hardware control and data acquisition in the `ucl-open` ecosystem. + +--- + +## Core repositories + +`acquisition` is part of a small set of tightly related core repositories: + +- **[ucl-open-rigs](https://github.com/ucl-open/ucl-open-rigs)** – shared, versioned descriptions of experimental rigs +- **[rig-template](https://github.com/ucl-open/rig-template)** – the primary entry point for creating new experiment repositories, using Copier +- **acquisition** – Bonsai workflows and operators for hardware control and data acquisition (this repository) + +Each of these repositories is developed in tandem and is dependent on the others. As development is ongoing, published versions of each are intended to be locked to one another; for example, `v0.1.0` of `acquisition` is compatible with `v0.1.0` of `ucl-open-rigs` and `rig-template`. + +In general, lab members and experimentalists will not work directly on this repository. Instead, `acquisition` is published as a Bonsai package (via NuGet) and consumed automatically by experiment repositories created using the `rig-template`. + +## What this repository is + +`acquisition` provides: + +- Reusable Bonsai workflows for common acquisition patterns +- Operators and abstractions for interfacing with hardware + +The repository focuses on *how data is acquired*, not on *what an experiment does with that data*. + +--- + +## What this repository is not + +`acquisition` is **not**: + +- An experiment repository +- A place for task logic or behavioural protocols +- A repository of rig or wiring descriptions + +Experiment-specific logic belongs in experiment repositories. + +--- + +## Repository structure (conceptual) + +While details may evolve, the repository contains: + +- **Reusable workflows** – common acquisition patterns composed from operators +- **Operators** – low-level abstractions over hardware or data streams +- **Examples** – minimal Bonsai workflows demonstrating intended usage + +The emphasis is on *composition*: complex acquisition behaviour is built from small, reusable operators rather than monolithic workflows. + +--- + +## Using this repository + +In most cases, you will **not interact with this repository directly**. + +The most common usage is **automatic**, via: + +- An experiment repository created from the [rig-template](https://github.com/ucl-open/rig-template) +- The template’s dependency configuration, which pulls in `acquisition` +- Integration with the [ucl-open-rigs](https://github.com/ucl-open/ucl-open-rigs) repository + +Acquisition workflows are added as a dependency, version-pinned by default, and available immediately within Bonsai without manual installation. + +In general, lab members and experimentalists will encounter `acquisition` only indirectly via the template and acquisition stack, rather than by cloning and working with this repository directly. + +You typically work directly with this repository only when adding reusable workflows or operators, extending support for new hardware, or improving shared abstractions. + +If you find yourself copying workflows out of this repository, you are most likely doing something outside the intended framework. + +--- + +## Relationship to ucl-open-rigs + +`acquisition` is designed to work *directly* with [ucl-open-rigs](https://github.com/ucl-open/ucl-open-rigs). + +In particular: + +- Workflows assume the rig contracts defined in `ucl-open-rigs` +- Device names, channels, and capabilities are resolved via rig definitions +- Changes to hardware or wiring are handled by updating the rig, not by editing workflows + +--- + +## How it fits into experiments + +Typical usage looks like this: + +1. An experiment repository is created from the [rig-template](https://github.com/ucl-open/rig-template) +2. The template pulls in `acquisition` and `ucl-open-rigs`, with versions locked to each other +3. A rig definition is extended from the template +4. Acquisition workflows are built in Bonsai using `acquisition` operators +5. Experiments trigger and coordinate acquisition, but do not reimplement it + +--- + +## Versioning and dependency locking + +Typical practice is: + +- Experiment repositories lock specific versions (tags or commits) against **ucl-open-rigs** +- Updates are pulled deliberately and tested against known rigs +- Breaking changes require an explicit version bump + +This ensures acquisition behaviour remains reproducible and consistent with historical data. + +--- + +## Using this repository + +You typically work directly with this repository only when: + +- Adding new reusable acquisition workflows or operators +- Extending support for new classes of hardware +- Improving shared abstractions used across labs + +If you find yourself copying workflows into experiment repositories, there is usually something wrong. diff --git a/docs/bonsai-docfx b/docs/bonsai-docfx new file mode 160000 index 0000000..d33401b --- /dev/null +++ b/docs/bonsai-docfx @@ -0,0 +1 @@ +Subproject commit d33401b473d647237d88b3eaa73b0dae3fc08ea7 diff --git a/docs/build.ps1 b/docs/build.ps1 new file mode 100644 index 0000000..01ad8f5 --- /dev/null +++ b/docs/build.ps1 @@ -0,0 +1,19 @@ +[CmdletBinding()] param ( + [string[]]$docfxArgs +) +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +Push-Location $PSScriptRoot +try { + $libPaths = @() + $libPaths += Get-ChildItem "..\artifacts\bin\*\release_net4*" -Directory | Select-Object -Expand FullName + $libPaths += "..\artifacts\package\release" + + ./export-images.ps1 $libPaths + dotnet docfx metadata + dotnet docfx build $docfxArgs +} finally { + Pop-Location +} diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..219094e --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "metadata": [ + { + "src": [ + { + "src": "../src/", + "files": "**/*.csproj", + "exclude": "**/*.Tests.csproj" + } + ], + "output": "../artifacts/docs/api/", + "enumSortOrder": "declaringOrder", + "memberLayout": "separatePages", + "filter": "filter.yml" + } + ], + "build": { + "content": [ + { + "files": [ + "*.md", + "toc.yml", + "{articles,tutorials,examples}/**/*.md", + "{articles,tutorials,examples}/**/toc.yml" + ], + "exclude": "README.md" + }, + { + "src": "../artifacts/docs/api/", + "dest": "api", + "files": "**/*.yml" + } + ], + "resource": [ + { + "files": [ + "images/**", + "workflows/**/*.{bonsai,svg}", + "{articles,tutorials,examples}/**/*.{bonsai,svg}" + ] + }, + { + "src": "../build/", + "files": "icon.png" + } + ], + "overwrite": [ + "apidoc/**/*.md" + ], + "output": "../artifacts/docs/site/", + "template": [ + "default", + "modern", + "bonsai-docfx/template", + "template" + ], + "sitemap": { + "baseUrl": "https://ucl-open.github.io/acquisition" + }, + "globalMetadata": { + "_appName": "UclOpen.Acquisition", + "_appTitle": "UclOpen.Acquisition", + "_appFooter": "© University College London and Contributors. Made with docfx", + "_appLogoPath": "logo.svg", + "_appFaviconPath": "icon.png", + "_enableNewTab": true, + "_enableSearch": true, + "_gitContribute": { + "apiSpecFolder": "docs/apidoc" + } + }, + "markdownEngineProperties": { + "markdigExtensions": [ + "attributes", + "customcontainers" + ] + }, + "xref": [ + "https://bonsai-rx.org/docs/xrefmap.yml", + "https://horizongir.github.io/reactive/xrefmap.yml" + ] + } +} \ No newline at end of file diff --git a/docs/export-images.ps1 b/docs/export-images.ps1 new file mode 100644 index 0000000..e613b87 --- /dev/null +++ b/docs/export-images.ps1 @@ -0,0 +1,44 @@ +[CmdletBinding()] param ( + [string[]]$LibrarySources, + [bool]$UseGalleryForWorkflowsDirectory=$false, + [bool]$UseGalleryForExamplesDirectory=$true, + [string]$OutputFolder=$null +) +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +if ($OutputFolder) { + $OutputFolder = Join-Path (Get-Location) $OutputFolder +} + +function Process-Workflow-Collection([bool]$useGallery, [string]$workflowPath, [string]$environmentPath) { + $libPath = $LibrarySources + + if ($useGallery) { + $libPath = @() + $galleryPath = Join-Path $environmentPath 'Gallery' + $null = New-Item -ItemType Directory -Path $galleryPath -Force + foreach ($librarySource in $LibrarySources) { + Get-ChildItem -Path $librarySource -Filter *.nupkg | Copy-Item -Destination $galleryPath + } + } + + $bootstrapperPath = (Join-Path $environmentPath 'Bonsai.exe') + .\bonsai-docfx\modules\Export-Image.ps1 -libPath $libPath -workflowPath $workflowPath -bootstrapperPath $bootstrapperPath -outputFolder $OutputFolder -documentationRoot $PSScriptRoot +} + +Push-Location $PSScriptRoot +try { + if (Test-Path -Path 'workflows/') { + Process-Workflow-Collection $UseGalleryForWorkflowsDirectory './workflows' '../.bonsai/' + } + + if (Test-Path -Path 'examples/') { + foreach ($environment in (Get-ChildItem -Path 'examples/' -Filter '.bonsai' -Recurse -FollowSymlink -Directory)) { + Process-Workflow-Collection $UseGalleryForExamplesDirectory ($environment.Parent.FullName) ($environment.FullName) + } + } +} finally { + Pop-Location +} diff --git a/docs/filter.yml b/docs/filter.yml new file mode 100644 index 0000000..472eb43 --- /dev/null +++ b/docs/filter.yml @@ -0,0 +1,4 @@ +apiRules: +- exclude: + hasAttribute: + uid: System.ObsoleteAttribute diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ba991fe --- /dev/null +++ b/docs/index.md @@ -0,0 +1,5 @@ +--- +_layout: landing +--- + +[!INCLUDE [](README.md)] diff --git a/docs/template/public/main.css b/docs/template/public/main.css new file mode 100644 index 0000000..fdb72e4 --- /dev/null +++ b/docs/template/public/main.css @@ -0,0 +1 @@ +@import "bonsai.css"; diff --git a/docs/template/public/main.js b/docs/template/public/main.js new file mode 100644 index 0000000..af7fafa --- /dev/null +++ b/docs/template/public/main.js @@ -0,0 +1,13 @@ +import WorkflowContainer from "./workflow.js" + +export default { + defaultTheme: 'light', + iconLinks: [{ + icon: 'github', + href: 'https://github.com/ucl-open/acquisition', + title: 'GitHub' + }], + start: () => { + WorkflowContainer.init(); + } +} diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..3daf8b9 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,2 @@ +- name: API + href: ../artifacts/docs/api/ diff --git a/docs/workflows/.gitignore b/docs/workflows/.gitignore new file mode 100644 index 0000000..e2beea4 --- /dev/null +++ b/docs/workflows/.gitignore @@ -0,0 +1,2 @@ +*.layout +*.svg diff --git a/global.json b/global.json new file mode 100644 index 0000000..989a69c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestMinor" + } +} \ No newline at end of file diff --git a/protocols/README.md b/protocols/README.md deleted file mode 100644 index c8979b1..0000000 --- a/protocols/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Protocols - -Workflows specific to individual behavioural protocols, or a general protocol structure, used in the lab. For example, a "AFC task" or "Passive Presentation of a Stimulus Set." - -## Adding New Protocols - -To add a new protocol to this repository: - -1. **Create a folder** with a descriptive name (e.g., `afc-task`, `passive-stimulus-presentation`) -2. **Add a README.md** file in the folder that includes: - - Screenshot of the workflow - - Description of the protocol - - Instructions for editing the associated .yaml file - - Output file specifications -3. **Include the workflow file(s)** (.bonsai files) -4. **Add any configuration files** needed for the protocol -5. **Test the protocol** with actual hardware before committing diff --git a/src/UclOpen.Core/ConvertByteArrayToString.cs b/src/UclOpen.Core/ConvertByteArrayToString.cs new file mode 100644 index 0000000..3663959 --- /dev/null +++ b/src/UclOpen.Core/ConvertByteArrayToString.cs @@ -0,0 +1,22 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Text; + +[Combinator] +[Description("")] +[WorkflowElementCategory(ElementCategory.Transform)] +public class ConvertByteArrayToString +{ + public IObservable Process(IObservable source) + { + return source.Select(value => + { + string String = Encoding.ASCII.GetString(value); + return String; + }); + } +} diff --git a/src/UclOpen.Core/CreateDigitalOutputs.cs b/src/UclOpen.Core/CreateDigitalOutputs.cs new file mode 100644 index 0000000..21968f3 --- /dev/null +++ b/src/UclOpen.Core/CreateDigitalOutputs.cs @@ -0,0 +1,31 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Harp.Behavior; + +[Combinator] +[WorkflowElementCategory(ElementCategory.Transform)] +public class CreateDigitalOutputs +{ + + public IObservable Process(IObservable> source) +{ + return source.Select(list => + { + DigitalOutputs outputs = DigitalOutputs.None; + + foreach (var token in list) + { + outputs |= (DigitalOutputs)Enum.Parse( + typeof(DigitalOutputs), + token.Trim() + ); + } + + return outputs; + }); +} +} diff --git a/src/UclOpen.Core/CreateLickSpoutStageMessage.cs b/src/UclOpen.Core/CreateLickSpoutStageMessage.cs new file mode 100644 index 0000000..253dab7 --- /dev/null +++ b/src/UclOpen.Core/CreateLickSpoutStageMessage.cs @@ -0,0 +1,36 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Text; + +namespace UclOpen.Core +{ + [Combinator] + [Description("")] + [WorkflowElementCategory(ElementCategory.Transform)] + public class CreateStepperMessage + { + public IObservable Process(IObservable> source) + { + return source.Select(value => + { + // Convert string to byte array using ASCII encoding + byte[] stringBytes = Encoding.ASCII.GetBytes(value.Item1); + + // Create result array with size = 1 + string byte length + byte[] bytes = new byte[1 + stringBytes.Length]; + + // Add the single byte at the beginning + bytes[0] = value.Item2; + + // Copy string bytes to the result array starting at index 1 + Array.Copy(stringBytes, 0, bytes, 1, stringBytes.Length); + + return bytes; + }); + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Core/CreateRepository.cs b/src/UclOpen.Core/CreateRepository.cs new file mode 100644 index 0000000..58173b8 --- /dev/null +++ b/src/UclOpen.Core/CreateRepository.cs @@ -0,0 +1,28 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Reactive.Linq; +using LibGit2Sharp; + +namespace UclOpen.Core +{ + [DefaultProperty(nameof(Path))] + [Description("Creates a repository object from the specified working directory or .git folder.")] + public class CreateRepository : Source + { + [Description("The relative or absolute path of the repository directory.")] + [Editor("Bonsai.Design.FolderNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string Path { get; set; } = "..\\..\\"; + + + public override IObservable Generate() + { + return Observable.Defer(() => + { + var path = string.IsNullOrEmpty(Path) ? Environment.CurrentDirectory : Path; + Repository repo = new Repository(path); + return Observable.Return(repo); + }); + } + } +} diff --git a/src/UclOpen.Core/CreateSoftwareEvent.cs b/src/UclOpen.Core/CreateSoftwareEvent.cs new file mode 100644 index 0000000..ce7f3be --- /dev/null +++ b/src/UclOpen.Core/CreateSoftwareEvent.cs @@ -0,0 +1,52 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai.Harp; +using UclOpen.Core.DataTypes; + +namespace UclOpen.Core +{ + [Combinator] + [Description("Creates a fully populated SoftwareEvent a generic object. If a Harp.Timestamped is provided, temporal information will be added.")] + [WorkflowElementCategory(ElementCategory.Transform)] + public class CreateSoftwareEvent + { + public string EventName { get; set; } = "SoftwareEvent"; + + public IObservable Process(IObservable> source) + { + var thisName = EventName; + return source.Select(value => + { + return new SoftwareEvent + { + Data = value.Value, + Timestamp = value.Seconds, + TimestampSource = TimestampSource.Harp, + FrameIndex = null, + FrameTimestamp = null, + Name = thisName, + }; + }); + } + + public IObservable Process(IObservable source) + { + var thisName = EventName; + return source.Select(value => + { + return new SoftwareEvent + { + Data = value, + Timestamp = null, + TimestampSource = TimestampSource.Null, + FrameIndex = null, + FrameTimestamp = null, + Name = thisName, + }; + }); + } + } +} diff --git a/src/UclOpen.Core/DataSchemas/ucl_open_data_types.json b/src/UclOpen.Core/DataSchemas/ucl_open_data_types.json new file mode 100644 index 0000000..030e2fc --- /dev/null +++ b/src/UclOpen.Core/DataSchemas/ucl_open_data_types.json @@ -0,0 +1,109 @@ +{ + "$defs": { + "SoftwareEvent": { + "description": "A software event is a generic event that can be used to track any event that occurs in the software.", + "properties": { + "name": { + "description": "The name of the event.", + "title": "Name", + "type": "string" + }, + "timestamp": { + "default": null, + "description": "The timestamp of the event.", + "title": "Timestamp", + "type": [ + "number", + "null" + ] + }, + "timestampSource": { + "$ref": "#/$defs/TimestampSource", + "default": "null", + "description": "The source of the timestamp. Typically either a harp device or on the visual render loop" + }, + "frameIndex": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The frame index of the event.", + "title": "FrameIndex" + }, + "frameTimestamp": { + "default": null, + "description": "The timestamp of the frame.", + "title": "FrameTimestamp", + "type": [ + "number", + "null" + ] + }, + "data": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "default": null, + "description": "The data payload of the event.", + "title": "Data" + } + }, + "required": [ + "name" + ], + "title": "SoftwareEvent", + "type": "object" + }, + "TimestampSource": { + "enum": [ + "null", + "harp", + "render", + "arduino" + ], + "title": "TimestampSource", + "type": "string" + }, + "Vector3": { + "properties": { + "x": { + "description": "X coordinate of the point.", + "title": "X", + "type": "number" + }, + "y": { + "description": "Y coordinate of the point.", + "title": "Y", + "type": "number" + }, + "z": { + "description": "Z coordinate of the point.", + "title": "Z", + "type": "number" + } + }, + "required": [ + "x", + "y", + "z" + ], + "title": "Vector3", + "type": "object" + } + }, + "required": [ + "vector3", + "softwareEvents" + ], + "title": "DataTypes", + "type": "object" +} \ No newline at end of file diff --git a/src/UclOpen.Core/DataTypes.Generated.cs b/src/UclOpen.Core/DataTypes.Generated.cs new file mode 100644 index 0000000..a689442 --- /dev/null +++ b/src/UclOpen.Core/DataTypes.Generated.cs @@ -0,0 +1,381 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- + + +namespace UclOpen.Core.DataTypes +{ + #pragma warning disable // Disable all warnings + + /// + /// A software event is a generic event that can be used to track any event that occurs in the software. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.7.2.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("A software event is a generic event that can be used to track any event that occu" + + "rs in the software.")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class SoftwareEvent + { + + private string _name; + + private double? _timestamp; + + private TimestampSource _timestampSource; + + private int? _frameIndex; + + private double? _frameTimestamp; + + private object _data; + + public SoftwareEvent() + { + _timestampSource = TimestampSource.Null; + } + + protected SoftwareEvent(SoftwareEvent other) + { + _name = other._name; + _timestamp = other._timestamp; + _timestampSource = other._timestampSource; + _frameIndex = other._frameIndex; + _frameTimestamp = other._frameTimestamp; + _data = other._data; + } + + /// + /// The name of the event. + /// + [Newtonsoft.Json.JsonPropertyAttribute("name", Required=Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DescriptionAttribute("The name of the event.")] + public string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } + + /// + /// The timestamp of the event. + /// + [Newtonsoft.Json.JsonPropertyAttribute("timestamp")] + [System.ComponentModel.DescriptionAttribute("The timestamp of the event.")] + public double? Timestamp + { + get + { + return _timestamp; + } + set + { + _timestamp = value; + } + } + + /// + /// The source of the timestamp. Typically either a harp device or on the visual render loop + /// + [Newtonsoft.Json.JsonPropertyAttribute("timestampSource")] + [System.ComponentModel.DescriptionAttribute("The source of the timestamp. Typically either a harp device or on the visual rend" + + "er loop")] + public TimestampSource TimestampSource + { + get + { + return _timestampSource; + } + set + { + _timestampSource = value; + } + } + + /// + /// The frame index of the event. + /// + [Newtonsoft.Json.JsonPropertyAttribute("frameIndex")] + [System.ComponentModel.DescriptionAttribute("The frame index of the event.")] + public int? FrameIndex + { + get + { + return _frameIndex; + } + set + { + _frameIndex = value; + } + } + + /// + /// The timestamp of the frame. + /// + [Newtonsoft.Json.JsonPropertyAttribute("frameTimestamp")] + [System.ComponentModel.DescriptionAttribute("The timestamp of the frame.")] + public double? FrameTimestamp + { + get + { + return _frameTimestamp; + } + set + { + _frameTimestamp = value; + } + } + + /// + /// The data payload of the event. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("data")] + [System.ComponentModel.DescriptionAttribute("The data payload of the event.")] + public object Data + { + get + { + return _data; + } + set + { + _data = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new SoftwareEvent(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new SoftwareEvent(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("Name = " + _name + ", "); + stringBuilder.Append("Timestamp = " + _timestamp + ", "); + stringBuilder.Append("TimestampSource = " + _timestampSource + ", "); + stringBuilder.Append("FrameIndex = " + _frameIndex + ", "); + stringBuilder.Append("FrameTimestamp = " + _frameTimestamp + ", "); + stringBuilder.Append("Data = " + _data); + return true; + } + + public override string ToString() + { + System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); + stringBuilder.Append(GetType().Name); + stringBuilder.Append(" { "); + if (PrintMembers(stringBuilder)) + { + stringBuilder.Append(" "); + } + stringBuilder.Append("}"); + return stringBuilder.ToString(); + } + } + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.7.2.0 (Newtonsoft.Json v13.0.0.0)")] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public enum TimestampSource + { + + [System.Runtime.Serialization.EnumMemberAttribute(Value="null")] + Null = 0, + + [System.Runtime.Serialization.EnumMemberAttribute(Value="harp")] + Harp = 1, + + [System.Runtime.Serialization.EnumMemberAttribute(Value="render")] + Render = 2, + + [System.Runtime.Serialization.EnumMemberAttribute(Value="arduino")] + Arduino = 3, + } + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.7.2.0 (Newtonsoft.Json v13.0.0.0)")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class Vector3 + { + + private double _x; + + private double _y; + + private double _z; + + public Vector3() + { + } + + protected Vector3(Vector3 other) + { + _x = other._x; + _y = other._y; + _z = other._z; + } + + /// + /// X coordinate of the point. + /// + [Newtonsoft.Json.JsonPropertyAttribute("x", Required=Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DescriptionAttribute("X coordinate of the point.")] + public double X + { + get + { + return _x; + } + set + { + _x = value; + } + } + + /// + /// Y coordinate of the point. + /// + [Newtonsoft.Json.JsonPropertyAttribute("y", Required=Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DescriptionAttribute("Y coordinate of the point.")] + public double Y + { + get + { + return _y; + } + set + { + _y = value; + } + } + + /// + /// Z coordinate of the point. + /// + [Newtonsoft.Json.JsonPropertyAttribute("z", Required=Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DescriptionAttribute("Z coordinate of the point.")] + public double Z + { + get + { + return _z; + } + set + { + _z = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new Vector3(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new Vector3(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("X = " + _x + ", "); + stringBuilder.Append("Y = " + _y + ", "); + stringBuilder.Append("Z = " + _z); + return true; + } + + public override string ToString() + { + System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); + stringBuilder.Append(GetType().Name); + stringBuilder.Append(" { "); + if (PrintMembers(stringBuilder)) + { + stringBuilder.Append(" "); + } + stringBuilder.Append("}"); + return stringBuilder.ToString(); + } + } + + + /// + /// Serializes a sequence of data model objects into JSON strings. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.7.2.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("Serializes a sequence of data model objects into JSON strings.")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] + [Bonsai.CombinatorAttribute()] + public partial class SerializeToJson + { + + public Newtonsoft.Json.Formatting Formatting { get; set; } + + private System.IObservable Process(System.IObservable source) + { + var formatting = Formatting; + return System.Reactive.Linq.Observable.Select(source, value => Newtonsoft.Json.JsonConvert.SerializeObject(value, formatting)); + } + + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + } + + + /// + /// Deserializes a sequence of JSON strings into data model objects. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.7.2.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("Deserializes a sequence of JSON strings into data model objects.")] + [System.ComponentModel.DefaultPropertyAttribute("Type")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + public partial class DeserializeFromJson : Bonsai.Expressions.SingleArgumentExpressionBuilder + { + + public DeserializeFromJson() + { + Type = new Bonsai.Expressions.TypeMapping(); + } + + public Bonsai.Expressions.TypeMapping Type { get; set; } + + public override System.Linq.Expressions.Expression Build(System.Collections.Generic.IEnumerable arguments) + { + var typeMapping = (Bonsai.Expressions.TypeMapping)Type; + var returnType = typeMapping.GetType().GetGenericArguments()[0]; + return System.Linq.Expressions.Expression.Call( + typeof(DeserializeFromJson), + "Process", + new System.Type[] { returnType }, + System.Linq.Enumerable.Single(arguments)); + } + + private static System.IObservable Process(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, value => Newtonsoft.Json.JsonConvert.DeserializeObject(value)); + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Core/FormatDate.cs b/src/UclOpen.Core/FormatDate.cs new file mode 100644 index 0000000..a107260 --- /dev/null +++ b/src/UclOpen.Core/FormatDate.cs @@ -0,0 +1,23 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; + +namespace UclOpen.Core +{ + [Combinator] + [Description("Formats a date time into a string with no illegal path characters.")] + [WorkflowElementCategory(ElementCategory.Transform)] + public class FormatDate + { + public IObservable Process(IObservable source) + { + return source.Select(value => + { + var result = value.ToString("o").Replace(':', '-'); + return result.Substring(0, result.IndexOf('.')); + }); + } + } +} diff --git a/src/UclOpen.Core/GroupByTime.cs b/src/UclOpen.Core/GroupByTime.cs new file mode 100644 index 0000000..e10c3f2 --- /dev/null +++ b/src/UclOpen.Core/GroupByTime.cs @@ -0,0 +1,105 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai.Harp; +using System.Xml.Serialization; +using System.Xml; + +namespace UclOpen.Core +{ + [Combinator] + [Description("Groups Harp time-series in whole hour chunks of fixed size.")] + [WorkflowElementCategory(ElementCategory.Combinator)] + public class GroupByTime + { + public GroupByTime() + { + ChunkSize = 1; + } + + // The default real-time reference is unix time in total seconds from 1904 + internal static readonly DateTime ReferenceTime = new(1904, 1, 1); + + [Description("The size of each chunk, in whole hours.")] + public int ChunkSize { get; set; } + + [XmlIgnore] + [Description("The relative time at which each group will close following the end of the chunk.")] + public TimeSpan? ClosingDuration { get; set; } + + [Browsable(false)] + [XmlElement(nameof(ClosingDuration))] + public string ClosingDurationXml + { + get + { + var timeShift = ClosingDuration; + if (timeShift.HasValue) return XmlConvert.ToString(timeShift.Value); + else return null; + } + set + { + if (!string.IsNullOrEmpty(value)) ClosingDuration = XmlConvert.ToTimeSpan(value); + else ClosingDuration = null; + } + } + + DateTime GetChunkIndex(double seconds) + { + var currentTime = ReferenceTime.AddSeconds(seconds); + var timeBin = currentTime.Hour / ChunkSize; + return currentTime.Date.AddHours(timeBin * ChunkSize); + } + + public IObservable>> Process(IObservable> source) + { + return source.GroupBy(value => GetChunkIndex(value.Seconds)); + } + + public IObservable> Process(IObservable source) + { + return source.GroupBy(value => GetChunkIndex(value.GetTimestamp())); + } + + public IObservable>> Process(IObservable> source) + { + return source.GroupBy(value => GetChunkIndex(value.Item2), value => Timestamped.Create(value.Item1, value.Item2)); + } + + bool ShouldCloseChunk(IGroupedObservable chunk, HarpMessage heartbeat) + { + var beatTimestamp = ReferenceTime.AddSeconds(heartbeat.GetTimestamp()); + var beatDelta = beatTimestamp - chunk.Key; + return beatDelta > new TimeSpan(ChunkSize, 0, 0) + ClosingDuration; + } + + public IObservable>> Process( + IObservable> source, + IObservable heartbeats) + { + return source.GroupByUntil( + value => GetChunkIndex(value.Seconds), + chunk => heartbeats.FirstOrDefaultAsync(message => ShouldCloseChunk(chunk, message))); + } + + public IObservable> Process( + IObservable source, + IObservable heartbeats) + { + return source.GroupByUntil( + value => GetChunkIndex(value.GetTimestamp()), + chunk => heartbeats.FirstOrDefaultAsync(message => ShouldCloseChunk(chunk, message))); + } + + public IObservable>> Process( + IObservable> source, + IObservable heartbeats) + { + return source.GroupByUntil( + value => GetChunkIndex(value.Item2), value => Timestamped.Create(value.Item1, value.Item2), + chunk => heartbeats.FirstOrDefaultAsync(message => ShouldCloseChunk(chunk, message))); + } + } +} diff --git a/src/UclOpen.Core/Properties/launchSettings.json b/src/UclOpen.Core/Properties/launchSettings.json new file mode 100644 index 0000000..4af4f46 --- /dev/null +++ b/src/UclOpen.Core/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(BonsaiExecutablePath)", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Core/StripSubstring.cs b/src/UclOpen.Core/StripSubstring.cs new file mode 100644 index 0000000..3ccdb86 --- /dev/null +++ b/src/UclOpen.Core/StripSubstring.cs @@ -0,0 +1,25 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; + +namespace UclOpen.Core +{ + [Combinator] + [Description("Selects the substring preceding the first occurrence of the specified separator.")] + [WorkflowElementCategory(ElementCategory.Transform)] + public class StripSubstring + { + public string Separator { get; set; } + + public IObservable Process(IObservable source) + { + return source.Select(value => + { + var parts = value.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries); + return parts.Length > 0 ? parts[0] : value; + }); + } + } +} diff --git a/src/UclOpen.Core/UclOpen.Core.csproj b/src/UclOpen.Core/UclOpen.Core.csproj new file mode 100644 index 0000000..21b1ae2 --- /dev/null +++ b/src/UclOpen.Core/UclOpen.Core.csproj @@ -0,0 +1,30 @@ + + + + A package providing common classes and types for UclOpen experiments. + $(PackageTags) Core + net472 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utilities/LED-Driver/LedDriver.bonsai b/src/UclOpen.Devices/ArduinoLedDriver.bonsai similarity index 60% rename from utilities/LED-Driver/LedDriver.bonsai rename to src/UclOpen.Devices/ArduinoLedDriver.bonsai index ecee3c2..372317e 100644 --- a/utilities/LED-Driver/LedDriver.bonsai +++ b/src/UclOpen.Devices/ArduinoLedDriver.bonsai @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ard="clr-namespace:Bonsai.Arduino;assembly=Bonsai.Arduino" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" + xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -10,17 +11,7 @@ - - - - - - - - - - - + @@ -30,24 +21,11 @@ 0 - - - false - - - - - PT1S - - - - LedTrigger - - - LedTrigger + + LedTrigger @@ -61,17 +39,11 @@ - - + - + + - - - - - - \ No newline at end of file diff --git a/utilities/device-behavior-board/BehaviorBoard.bonsai b/src/UclOpen.Devices/BehaviorBoard/BehaviorBoard.bonsai similarity index 100% rename from utilities/device-behavior-board/BehaviorBoard.bonsai rename to src/UclOpen.Devices/BehaviorBoard/BehaviorBoard.bonsai diff --git a/utilities/device-behavior-board/add.cameraTriggerController/CameraTriggerController.bonsai b/src/UclOpen.Devices/BehaviorBoard/CameraTriggerController.bonsai similarity index 100% rename from utilities/device-behavior-board/add.cameraTriggerController/CameraTriggerController.bonsai rename to src/UclOpen.Devices/BehaviorBoard/CameraTriggerController.bonsai diff --git a/utilities/device-behavior-board/add.cameraTriggerController/README.md b/src/UclOpen.Devices/BehaviorBoard/CameraTriggerController/README.md similarity index 100% rename from utilities/device-behavior-board/add.cameraTriggerController/README.md rename to src/UclOpen.Devices/BehaviorBoard/CameraTriggerController/README.md diff --git a/utilities/device-behavior-board/add.cameraTriggerController/assets/CameraTriggerController.svg b/src/UclOpen.Devices/BehaviorBoard/CameraTriggerController/assets/CameraTriggerController.svg similarity index 100% rename from utilities/device-behavior-board/add.cameraTriggerController/assets/CameraTriggerController.svg rename to src/UclOpen.Devices/BehaviorBoard/CameraTriggerController/assets/CameraTriggerController.svg diff --git a/utilities/device-behavior-board/add.pulseController/PulseController.bonsai b/src/UclOpen.Devices/BehaviorBoard/PulseController.bonsai similarity index 100% rename from utilities/device-behavior-board/add.pulseController/PulseController.bonsai rename to src/UclOpen.Devices/BehaviorBoard/PulseController.bonsai diff --git a/utilities/device-behavior-board/add.pulseController/README.md b/src/UclOpen.Devices/BehaviorBoard/PulseController/README.md similarity index 100% rename from utilities/device-behavior-board/add.pulseController/README.md rename to src/UclOpen.Devices/BehaviorBoard/PulseController/README.md diff --git a/utilities/device-behavior-board/add.pulseController/assets/PulseController.svg b/src/UclOpen.Devices/BehaviorBoard/PulseController/assets/PulseController.svg similarity index 100% rename from utilities/device-behavior-board/add.pulseController/assets/PulseController.svg rename to src/UclOpen.Devices/BehaviorBoard/PulseController/assets/PulseController.svg diff --git a/utilities/device-behavior-board/README.md b/src/UclOpen.Devices/BehaviorBoard/README.md similarity index 100% rename from utilities/device-behavior-board/README.md rename to src/UclOpen.Devices/BehaviorBoard/README.md diff --git a/utilities/running-wheel/RunningWheel.bonsai b/src/UclOpen.Devices/BehaviorBoard/RunningWheel.bonsai similarity index 65% rename from utilities/running-wheel/RunningWheel.bonsai rename to src/UclOpen.Devices/BehaviorBoard/RunningWheel.bonsai index f85a9e3..6dc3fdf 100644 --- a/utilities/running-wheel/RunningWheel.bonsai +++ b/src/UclOpen.Devices/BehaviorBoard/RunningWheel.bonsai @@ -5,22 +5,27 @@ xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp" - xmlns:ipy="clr-namespace:Bonsai.Scripting.IronPython;assembly=Bonsai.Scripting.IronPython" - xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" + xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns="https://bonsai-rx.org/2018/workflow"> Write - - EncoderPort2 + + EncoderPort2 Write - - EncoderPort2 + + Displacement + + + + Write + + EncoderPort2 @@ -44,6 +49,11 @@ BehaviorEvents + + + PT0.1S + + @@ -55,42 +65,25 @@ 1 - - @returns(int) -def process(value): - CAPACITY = 2**16 - MAX = CAPACITY/2 - MIN = -CAPACITY/2 - if value > MAX: - value -= CAPACITY - elif value < MIN: - value += CAPACITY - return value + + ConvertSigned + it > 32768.0 ? it - 65536.0 : (it < -32768.0 ? it + 65536.0 : it) - + TicksToCentimeters + Converts steps on a quadrature encoder to a distance in cm, published to a Subject Source1 - - - - - - - - -3440 - - - + @@ -98,7 +91,7 @@ def process(value): - + @@ -106,6 +99,7 @@ def process(value): + CircumferenceCm (it*Math.Pi)/10 @@ -121,38 +115,44 @@ def process(value): - - - - - + + + + + - + - - + + + - WheelDistance_mm + WheelDistanceCm - - - - - - - + + + + + - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/utilities/device-behavior-board/add.timestamps/Timestamps.bonsai b/src/UclOpen.Devices/BehaviorBoard/Timestamps.bonsai similarity index 100% rename from utilities/device-behavior-board/add.timestamps/Timestamps.bonsai rename to src/UclOpen.Devices/BehaviorBoard/Timestamps.bonsai diff --git a/utilities/device-behavior-board/assets/BehaviorBoard.svg b/src/UclOpen.Devices/BehaviorBoard/assets/BehaviorBoard.svg similarity index 100% rename from utilities/device-behavior-board/assets/BehaviorBoard.svg rename to src/UclOpen.Devices/BehaviorBoard/assets/BehaviorBoard.svg diff --git a/utilities/device-behavior-board/assets/harp-behavior-device.png b/src/UclOpen.Devices/BehaviorBoard/assets/harp-behavior-device.png similarity index 100% rename from utilities/device-behavior-board/assets/harp-behavior-device.png rename to src/UclOpen.Devices/BehaviorBoard/assets/harp-behavior-device.png diff --git a/src/UclOpen.Devices/LickSpoutStage.bonsai b/src/UclOpen.Devices/LickSpoutStage.bonsai new file mode 100644 index 0000000..a67671e --- /dev/null +++ b/src/UclOpen.Devices/LickSpoutStage.bonsai @@ -0,0 +1,360 @@ + + + Custom serial driver controlling a LickSpoutStage controling the positon of two lick spouts actuated by 5 stepper motors + + + + RigSettings + + + LickSpoutStage + + + + + + Value + + + + + + + + + + + + + StepperDriver + 9600 + \r\n + None + 63 + 8 + One + None + false + false + false + 4096 + 2048 + 1 + + + + + + + + + + SetSpeed + + + + + + + + 300 + + + + {0}; + it + + + + + + + + + + + + + + + + + 72 + + + + + + + + 1 + + + + + + + + StepperDriver + + + + + PT0.1S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SetAcceleration + + + + + + + + + 20 + + + + + + + + 2 + + + + + + + {0},{1}; + Item1,Item2 + + + + + + + + + 20,2; + + + + CommandByte_AdjustAcceleration + + + + + + + + 73 + + + + + + + + 1 + + + + + + + + StepperDriver + + + + + PT0.1S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ListenForMoveCommand + + + + + + + StepperCommands + + + + + + + 71 + + + + + + + + + + + + + + StepperDriver + + + + + + + + + + + + + + + + + + SetPositions + + + Positions + + + + home + + + + {0},{1},{2},{3},{4} + LeftElevation,RightElevation,RightRadial,LeftRadial,BaseTransverse + + + + + + + + + 0,0,0,0,0; + + + + StepperCommands + + + + StepperDriver + 1 + + + + + + + + + + + + + StepperEvents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utilities/device-lickety-split/LicketySplit.bonsai b/src/UclOpen.Devices/LicketySplit.bonsai similarity index 100% rename from utilities/device-lickety-split/LicketySplit.bonsai rename to src/UclOpen.Devices/LicketySplit.bonsai diff --git a/src/UclOpen.Devices/Properties/AssemblyInfo.cs b/src/UclOpen.Devices/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ea7bc91 --- /dev/null +++ b/src/UclOpen.Devices/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using Bonsai; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: XmlNamespacePrefix("clr-namespace:UclOpen.Devices", null)] diff --git a/src/UclOpen.Devices/Properties/launchSettings.json b/src/UclOpen.Devices/Properties/launchSettings.json new file mode 100644 index 0000000..4af4f46 --- /dev/null +++ b/src/UclOpen.Devices/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(BonsaiExecutablePath)", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Devices/SerialDevice.bonsai b/src/UclOpen.Devices/SerialDevice.bonsai new file mode 100644 index 0000000..7df7f70 --- /dev/null +++ b/src/UclOpen.Devices/SerialDevice.bonsai @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Scale + + + + + + + + + + + + + SerialDevice + + 9600 + \r\n + None + 63 + 8 + One + None + false + false + false + 4096 + 2048 + 1 + + + + + + + Read + + + + Source1 + + + Item2 + + + + 1 + + + + portname + + + portname + + + + + + + + + SerialDevice + + + + + + + + + + + + + + + + + + + SerialMessages + + + + + + SerialCommands + + + + Scale + \r\n + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utilities/triggered-camera-acquisition/TriggeredCameraAcquisition.bonsai b/src/UclOpen.Devices/TriggeredSpinnaker.bonsai similarity index 85% rename from utilities/triggered-camera-acquisition/TriggeredCameraAcquisition.bonsai rename to src/UclOpen.Devices/TriggeredSpinnaker.bonsai index 7332c05..9ae65c8 100644 --- a/utilities/triggered-camera-acquisition/TriggeredCameraAcquisition.bonsai +++ b/src/UclOpen.Devices/TriggeredSpinnaker.bonsai @@ -1,7 +1,7 @@  - - 1 - + + Default - 10000 - 1 - 1 + 19000 + 0 + 1 - - + + + + @@ -44,11 +45,11 @@ CameraTriggerEvents - + - 1 + 0 diff --git a/src/UclOpen.Devices/UclOpen.Devices.csproj b/src/UclOpen.Devices/UclOpen.Devices.csproj new file mode 100644 index 0000000..9a132a8 --- /dev/null +++ b/src/UclOpen.Devices/UclOpen.Devices.csproj @@ -0,0 +1,21 @@ + + + + A package providing common hardware and control functionality for all UCL Open experiments. + $(PackageTags) Devices + net472 + + + + + + + + + + + + + + + diff --git a/src/UclOpen.Logging/JsonWriter.cs b/src/UclOpen.Logging/JsonWriter.cs new file mode 100644 index 0000000..ccb22bc --- /dev/null +++ b/src/UclOpen.Logging/JsonWriter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Bonsai.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace UclOpen.Logging +{ + public class JsonWriter : StreamSink + { + protected override StreamWriter CreateWriter(Stream stream) + { + return new StreamWriter(stream); + } + + protected override void Write(StreamWriter writer, string input) + { + var parsed = JToken.Parse(input); + var formatted = parsed.ToString(Formatting.Indented); + writer.WriteLine(formatted); + } + } +} \ No newline at end of file diff --git a/logging/LogController.bonsai b/src/UclOpen.Logging/LogController.bonsai similarity index 96% rename from logging/LogController.bonsai rename to src/UclOpen.Logging/LogController.bonsai index 333717c..7501434 100644 --- a/logging/LogController.bonsai +++ b/src/UclOpen.Logging/LogController.bonsai @@ -4,7 +4,7 @@ xmlns:io="clr-namespace:Bonsai.IO;assembly=Bonsai.System" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" - xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:p1="clr-namespace:UclOpen.Core;assembly=UclOpen.Core" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -51,7 +51,7 @@ Timestamp.UtcDateTime - + diff --git a/logging/LogData.bonsai b/src/UclOpen.Logging/LogData.bonsai similarity index 94% rename from logging/LogData.bonsai rename to src/UclOpen.Logging/LogData.bonsai index 4310afb..c1678c5 100644 --- a/logging/LogData.bonsai +++ b/src/UclOpen.Logging/LogData.bonsai @@ -2,10 +2,9 @@ - Logs a generic timestamped data stream into the base data log. @@ -61,9 +60,7 @@ - - _ - + @@ -112,7 +109,7 @@ - false + true false None true diff --git a/logging/LogHarpDevice.bonsai b/src/UclOpen.Logging/LogHarpDevice.bonsai similarity index 97% rename from logging/LogHarpDevice.bonsai rename to src/UclOpen.Logging/LogHarpDevice.bonsai index ef583ca..d0b9a26 100644 --- a/logging/LogHarpDevice.bonsai +++ b/src/UclOpen.Logging/LogHarpDevice.bonsai @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" - xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:p1="clr-namespace:UclOpen.Core;assembly=UclOpen.Core" xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -77,9 +77,7 @@ - - _ - + @@ -295,9 +293,7 @@ - - _ - + diff --git a/logging/LogVideo.bonsai b/src/UclOpen.Logging/LogVideo.bonsai similarity index 94% rename from logging/LogVideo.bonsai rename to src/UclOpen.Logging/LogVideo.bonsai index a66dc95..b4afc35 100644 --- a/logging/LogVideo.bonsai +++ b/src/UclOpen.Logging/LogVideo.bonsai @@ -1,20 +1,19 @@  - Chunks and logs a timestamped video stream into the base data log. Source1 - - 24 + + 1 @@ -66,9 +65,7 @@ - - _ - + @@ -149,9 +146,7 @@ - - _ - + diff --git a/src/UclOpen.Logging/Properties/AssemblyInfo.cs b/src/UclOpen.Logging/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..37c7770 --- /dev/null +++ b/src/UclOpen.Logging/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using Bonsai; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: XmlNamespacePrefix("clr-namespace:UclOpen.Logging", null)] diff --git a/src/UclOpen.Logging/Properties/launchSettings.json b/src/UclOpen.Logging/Properties/launchSettings.json new file mode 100644 index 0000000..4af4f46 --- /dev/null +++ b/src/UclOpen.Logging/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(BonsaiExecutablePath)", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Logging/UclOpen.Logging.csproj b/src/UclOpen.Logging/UclOpen.Logging.csproj new file mode 100644 index 0000000..7a69277 --- /dev/null +++ b/src/UclOpen.Logging/UclOpen.Logging.csproj @@ -0,0 +1,22 @@ + + + + UclOpen Logging Bonsai library. + $(PackageTags) Logging + net472 + + + + + + + + + + + + + + + + diff --git a/src/UclOpen.Video/ObservableExtensions.cs b/src/UclOpen.Video/ObservableExtensions.cs new file mode 100644 index 0000000..52d0359 --- /dev/null +++ b/src/UclOpen.Video/ObservableExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; + +namespace UclOpen.Video +{ + internal static class ObservableExtensions + { + public static IObservable FillGaps(this IObservable source, Func gapSelector) + { + return FillGaps(source, value => value, gapSelector); + } + + public static IObservable FillGaps( + this IObservable source, + Func counterSelector, + Func gapSelector) + { + return Observable.Create(observer => + { + bool hasPrevious = false; + TCounter previousCounter = default; + var gapObserver = Observer.Create(value => + { + var counter = counterSelector(value); + if (hasPrevious) + { + var missing = gapSelector(previousCounter, counter); + if (missing < 0) + { + observer.OnError(new InvalidOperationException( + $"Negative gap sizes are not allowed.\n Previous counter: {previousCounter}\n Current counter: {counter}")); + } + + while (missing > 0) + { + observer.OnNext(default); + missing--; + } + } + observer.OnNext(value); + previousCounter = counter; + hasPrevious = true; + }); + return source.SubscribeSafe(gapObserver); + }); + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Video/Properties/launchSettings.json b/src/UclOpen.Video/Properties/launchSettings.json new file mode 100644 index 0000000..4af4f46 --- /dev/null +++ b/src/UclOpen.Video/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(BonsaiExecutablePath)", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/UclOpen.Video/TriggeredSpinnaker.cs b/src/UclOpen.Video/TriggeredSpinnaker.cs new file mode 100644 index 0000000..da27bd2 --- /dev/null +++ b/src/UclOpen.Video/TriggeredSpinnaker.cs @@ -0,0 +1,63 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai.Harp; +using SpinnakerNET; + +namespace UclOpen.Video +{ + [Description("Configures and initializes a Spinnaker camera for triggered acquisition.")] + public class SpinnakerCapture : Bonsai.Spinnaker.SpinnakerCapture + { + public SpinnakerCapture() + { + ExposureTime = 1e6 / 50 - 1000; + Binning = 1; + } + + [Description("The duration of each individual exposure, in microseconds. In general, this should be 1 / frameRate - 1 millisecond to prepare for next trigger.")] + public double ExposureTime { get; set; } + + [Description("The gain of the sensor.")] + public double Gain { get; set; } + + [Description("The size of the binning area of the sensor, e.g. a binning size of 2 specifies a 2x2 binning region.")] + public int Binning { get; set; } + + protected override void Configure(IManagedCamera camera) + { + try { camera.AcquisitionStop.Execute(); } + catch { } + camera.BinningSelector.Value = BinningSelectorEnums.All.ToString(); + camera.BinningHorizontalMode.Value = BinningHorizontalModeEnums.Sum.ToString(); + camera.BinningVerticalMode.Value = BinningVerticalModeEnums.Sum.ToString(); + camera.BinningHorizontal.Value = Binning; + camera.BinningVertical.Value = Binning; + camera.AcquisitionFrameRateEnable.Value = false; + camera.TriggerMode.Value = TriggerModeEnums.On.ToString(); + camera.TriggerSelector.Value = TriggerSelectorEnums.FrameStart.ToString(); + camera.TriggerSource.Value = TriggerSourceEnums.Line0.ToString(); + camera.TriggerOverlap.Value = TriggerOverlapEnums.ReadOut.ToString(); + camera.TriggerActivation.Value = TriggerActivationEnums.RisingEdge.ToString(); + camera.ExposureAuto.Value = ExposureAutoEnums.Off.ToString(); + camera.ExposureMode.Value = ExposureModeEnums.Timed.ToString(); + camera.ExposureTime.Value = ExposureTime; + camera.DeviceLinkThroughputLimit.Value = camera.DeviceLinkThroughputLimit.Max; + camera.GainAuto.Value = GainAutoEnums.Off.ToString(); + camera.Gain.Value = Gain; + base.Configure(camera); + } + + public IObservable> Generate(IObservable> source) + { + var frames = Generate(); + return frames + .Select(frame => new VideoDataFrame(frame.Image, frame.ChunkData.FrameID, frame.ChunkData.Timestamp)) + .FillGaps(frame => frame.ChunkData.FrameID, (previous, current) => (int)(current - previous - 1)) + .Zip(source, (frame, payload) => Timestamped.Create(frame, payload.Seconds)) + .Where(timestamped => timestamped.Value != null); + } + } +} diff --git a/src/UclOpen.Video/UclOpen.Video.csproj b/src/UclOpen.Video/UclOpen.Video.csproj new file mode 100644 index 0000000..c87ac9f --- /dev/null +++ b/src/UclOpen.Video/UclOpen.Video.csproj @@ -0,0 +1,21 @@ + + + + A package providing common video acquisition functionality for all UCL Open experiments. + $(PackageTags) Video + net472 + + + + + + + + + + + + + + + diff --git a/src/UclOpen.Video/VideoDataFrame.cs b/src/UclOpen.Video/VideoDataFrame.cs new file mode 100644 index 0000000..6ddb994 --- /dev/null +++ b/src/UclOpen.Video/VideoDataFrame.cs @@ -0,0 +1,35 @@ +using OpenCV.Net; + +namespace UclOpen.Video +{ + public class VideoDataFrame + { + public VideoDataFrame(IplImage image, long frameID, long timestamp) + : this(image, new VideoChunkData(frameID, timestamp)) + { + } + + public VideoDataFrame(IplImage image, VideoChunkData chunkData) + { + Image = image; + ChunkData = chunkData; + } + + public IplImage Image { get; } + + public VideoChunkData ChunkData { get; } + } + + public struct VideoChunkData + { + public VideoChunkData(long frameID, long timestamp) + { + FrameID = frameID; + Timestamp = timestamp; + } + + public long FrameID { get; } + + public long Timestamp { get; } + } +} diff --git a/src/UclOpen.Vision/SyncQuad/Controllers/RandomFlip.bonsai b/src/UclOpen.Vision/SyncQuad/Controllers/RandomFlip.bonsai new file mode 100644 index 0000000..ca8e2ac --- /dev/null +++ b/src/UclOpen.Vision/SyncQuad/Controllers/RandomFlip.bonsai @@ -0,0 +1,246 @@ + + + + + + RandomQuadFlip + + + + RigSchema + + + CreateRandom + + + + Source1 + + + + 1 + + + + RigSettings + + + RigSettings + + + + + + + + RigSettings + + + QuadTimeLowerBound + + + + + + + + RigSettings + + + QuadTimeUpperBound + + + + + + + + + 0.2 + 0.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + RandomSource + + + + Off + + + + RandomSource + + + + + + + 1 + + + + TimeSpan.FromSeconds(it) + + + Delay + + + Source1 + + + Delay + + + + + + + + + PT0.386S + + + + + 0 + + + + + 1 + + + + + + + + + + + + + + + + + + + + QuadState + + + On + + + + RandomSource + + + + + + + 1 + + + + TimeSpan.FromSeconds(it) + + + Delay + + + Source1 + + + Delay + + + + + + + + + PT0.458S + + + + + 1 + + + + + 1 + + + + + + + + + + + + + + + + + + + + QuadState + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UclOpen.Vision/SyncQuad/SyncQuad.bonsai b/src/UclOpen.Vision/SyncQuad/SyncQuad.bonsai new file mode 100644 index 0000000..1512bb0 --- /dev/null +++ b/src/UclOpen.Vision/SyncQuad/SyncQuad.bonsai @@ -0,0 +1,122 @@ + + + + + + + + + SyncQuadState + + + + + + + SyncQuadTrigger + + + -1 + 1 + -1 + 1 + + + RigSchema + + + SyncQuad.LocationX + + + + + + + + RigSchema + + + SyncQuad.LocationY + + + + + + + + RigSchema + + + SyncQuad.ExtentX + + + + + + + + RigSchema + + + SyncQuad.ExtentY + + + + + + + + SyncQuadState + + + + + + + + + + 0.5 + 0.5 + 1 + -1 + 1 + 0 + 0 + 0 + 0 + 1 + + + PostDrawQuad + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UclOpen.Vision/UclOpen.Vision.csproj b/src/UclOpen.Vision/UclOpen.Vision.csproj new file mode 100644 index 0000000..50c15c8 --- /dev/null +++ b/src/UclOpen.Vision/UclOpen.Vision.csproj @@ -0,0 +1,21 @@ + + + + A package providing common visual stimulus functionality for all UCL Open experiments. + $(PackageTags) Vision + net472 + + + + + + + + + + + + + + + diff --git a/utilities/Galvo-Driver/GalvoDriver.bonsai b/utilities/Galvo-Driver/GalvoDriver.bonsai deleted file mode 100644 index 252e0c5..0000000 --- a/utilities/Galvo-Driver/GalvoDriver.bonsai +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - 441 - 10000 - Sine - 10000 - 1 - 0 - 0 - - - - - - - - - 5000 - Rising - ContinuousSamples - 1000 - - - - -10 - 10 - Dev1/ao0 - Volts - - - - - - - - - - - 5000 - Rising - ContinuousSamples - 1000 - - - - -10 - 10 - Dev1/ao1 - Volts - - - - - - - - - - - - - - - - - - - - - diff --git a/utilities/Galvo-Driver/README.md b/utilities/Galvo-Driver/README.md deleted file mode 100644 index 6d4f1a8..0000000 --- a/utilities/Galvo-Driver/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Galvo Driver Workflow - -### Purpose -This workflow is to help control galvonometric mirrors in our experimental paradigm, but also can be used to initialise their use in your broader workflow. - -## Hardware Requirements -- National Instrument Data Acquisition (NIDAQ) device -- Galvonometric mirrors with assorted wiring, and power source unit - -Note: Depending on your current version of NIDAQ software you may not be able to run this workflow. Please depreciate to a version before or equal to 19.0 if possible. See: https://github.com/orgs/bonsai-rx/discussions/2094 - -## Bonsai Workflow -This workflow establishes basic commnication between your galvonometric mirrors and your workflow. - -workflow - -## Properties -The properties of this workflow allow the user to configure the parameters needed to control their galvonometric mirrors by using a simple function generator which can be customised. This workflow can be used to inititalise hardware in a wider experimental workflow. - - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `FunctionGenerator` | Input | The name of the input subject that sends commands to the device. | -| `AnalogOutput` | Output | The name of the subject that receives event messages from the device. | -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|-------------------------------------------------------------------------------------------------------| -| `Amplitude` | Input | Sets the amplitude of the signal waveform | -| `BufferLength` | Input | Sets the number of samples in each output | -| `Depth` | Input | Sets the bit depth of each element in the output | -| `DepthSpecified` | Input | Gets a value indicating whether Depth should be serialised | -| `Frequency` | Input | Sets the frequency of the signal waveform in Hz | -| `Offset` | Input | Sets an optional DC-offset of the signal waveform | -| `Phase` | Input | Sets an optional phase offset of the signal waveform in radians | -| `SampleRate` | Input | Sets the sampling rate of the generated signal waveform in Hz | -| `Waveform` | Input | Sets a value specifying the periodic waveform used to sample the signal | -| `BufferSize` | Input | Sets the number of samples to generate for finite samples, or size of buffer for continuous samples | -| `Channels` | Input | Gets the collection of analog output channels used to generate voltage signals | -| `SampleMode` | Input | Sets a value specifying whether a finite number of samples is generated or a continuous generation | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [DAQmx](https://www.nuget.org/packages/Bonsai.DAQmx) -- [Dsp] (https://www.nuget.org/packages/Bonsai.Dsp) -- [Dsp.Design](https://www.nuget.org/packages/Bonsai.Dsp) - - diff --git a/utilities/Galvo-Driver/image.png b/utilities/Galvo-Driver/image.png deleted file mode 100644 index 1790afe..0000000 Binary files a/utilities/Galvo-Driver/image.png and /dev/null differ diff --git a/utilities/LED-Driver/README.md b/utilities/LED-Driver/README.md deleted file mode 100644 index 475d0f8..0000000 --- a/utilities/LED-Driver/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# LED Driver Workflow - -### Purpose -This workflow is to help set up LEDs in our behavioural paradigm, but also can be used to initialise LEDs to be used however you wish to. - -## Hardware Requirements -- Arduino Board - -## Bonsai Workflow -This workflow establishes basic commnication between your LED devices and your workflow. - -workflow - -## Properties -The properties of this workflow allow the user to configure the parameters needed to control their LEDs by using Boolean logic. This workflow can be used to initialise this hardware in wider behavioural workflows. - - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `TriggerOn` | Input | The name of the input subject that sends commands to the device. | -| `TriggerOff` | Input | The name of the subject that receives event messages from the device. | - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `PortName` | Input | Serial port used to communicate with the device (e.g., COM14). | -| `Pin` | Output | Output pin number on which to write boolean state values | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [Arduino](https://www.nuget.org/packages/Bonsai.Arduino) \ No newline at end of file diff --git a/utilities/LED-Driver/assets/LedDriver.svg b/utilities/LED-Driver/assets/LedDriver.svg deleted file mode 100644 index dd76e8f..0000000 --- a/utilities/LED-Driver/assets/LedDriver.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]>ArduinoLedTriggerWorkflowOutputBaudRate,SamplingInterval,PortNaNameDelayDigitalOutputStringBooleanLedTriggerPinNameLedTriggerSubjectName \ No newline at end of file diff --git a/utilities/LED-Driver/image.png b/utilities/LED-Driver/image.png deleted file mode 100644 index c7caa7c..0000000 Binary files a/utilities/LED-Driver/image.png and /dev/null differ diff --git a/utilities/README.md b/utilities/README.md deleted file mode 100644 index 2a92d37..0000000 --- a/utilities/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Utilities - -Workflow "modules" that interface with hardware components. Examples could include camera acquisition, reward delivery, etc. - -## Adding New Hardware Modules - -To add a new hardware module to this repository: - -1. **Create a folder** with a descriptive name (e.g., `camera-acquisition`, `reward-delivery`) -2. **Add a README.md** file in the folder that includes: - - Screenshot of the workflow - - The hardware (with links) - - The inputs and outputs (and any assumed connection-pins etc.) - - Any user-parameters that need to be input (and their defaults) - - Output file specifications -3. **Include the workflow file(s)** (.bonsai files) -4. **Add any configuration files** needed for the hardware module -5. **Test the hardware module** with actual hardware before committing diff --git a/utilities/Speaker-Playback/README.md b/utilities/Speaker-Playback/README.md deleted file mode 100644 index 8fb8167..0000000 --- a/utilities/Speaker-Playback/README.md +++ /dev/null @@ -1,87 +0,0 @@ -### Speaker Playback Workflow - -### Purpose -This workflow exists to set up audio playback on a multi-speaker hardware configuration in Bonsai, which can be used for testing to check functionality and subsequent integration if needed into behavioural and experimental workflows. It can also be used for speaker calibration and hardware diagnostics. - -## Hardware Requirements -- Speakers -- BehaviorBoard - -## Bonsai Workflow -This workflow sets up the audio mixer and buffer pipeline for an audio stimulus to play on multiple speakers for testing purposes. - -workflow - -## Properties -The properties of this workflow allow the user to configure the parameters needed to communicate between their devices and produce a auditory stimulus of choice on a multi-speaker set-up. - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** |**Description** | -|-------------------------|---------------------------------------------------------------------------------------------| -| `Timer` | Input | The name of the subject generating a periodic observable output of an auditory stimulus | -| `Int` | Input | The name of the subject that represents a property containing an integer for initialising auditory stimulus state. | -| `Byte` | Input | The name of the subject that represents a property containing an integer for initialising speaker signal. | -| `BehaviorCommands` | Input | The name of the subject that receives and stores command messages from the BehaviorBoard | -| `BehaviorEvents` | Input | The name of the subject receiving events from BehaviorBoard | -| `Behavior` | Input | The name of the subject that initialises connections to BehaviorBoard and allows for configuration of hardware parameters | -| `Behavior.OutputClearPayload` | Input | The name of the subject that creates a message payload that clear the specified digital output lines. | -| `Behavior.OutputPulseEnablePayload` | Input | The name of the subject that creates a message payload that enables the pulse function on the board | -| `Behavior.PulseDO2Payload` | Input | The name of the subject that specifies the duration of the output pulse in milliseconds | -| `CreateMixerContent` | Input | The name of the subject that creates a new mixer stream to control simultaneous output of multiple audio buffers | -| `Buffer` | Input | The name of the subject that receives buffer content created in the workflow for playback. | -| `Mixer` | Input | The name of the subject that receives mixer content for buffer queueing from other parts of the workflow | -| `FunctionGenerator` | Input | The name of the subject that generates signal waveforms for any set of common periodic functions | -| `BehaviorCommands` | Output | The name of the subject that publishes merged command sequences for the board to use | -| `BehaviorEvents` | Output | The name of the subject that publishes events received from BehaviorBoard to other parts of the workflow | -| `Buffer` | Output | The name of the subject that publishes audio data to broadcasted subject nodes elsewhere in the workflow. | -| `AuditoryStarted` | Output | The name of the subject that broadcasts to other parts of the workflow whether the stimulus is actively playing or not | -| `Mixer` | Output | The name of the subject that stores and broadcasts mixer content for use by other parts of the workflow | -| `SpeakerSignal` | Output | The name of the subject that stores and publishes initialised signal to other places in workflow for usage | - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|-------------------------------------------------------------------------------------------------------| -| `DumpRegisters` | Input | Specifies whether the device should send content of all registers during initialisation | -| `Heartbeat` | Input | Specifies if the device sends the timestamp event each second | -| `IgnoreErrors` | Input | Specifies whether error messages parsed during acquisition should be ignored or raise an error | -| `OperationLed` | Input | Specifies the state of the LED reporting device operation | -| `OperationMode` | Input | Specifies the operation mode of the device at initialisation | -| `PortName` | Input | Specifies the serial port used to communicate with the device | -| `VisualIndicators` | Input | Specifies the state of visual indicators in the device | -| `MessageType` | Input | Specifies the type of created message | -| `OutputClear` | Input | Specifies the value the clears the specified digital output lines | -| `Payload` | Input | Specifies the operator used to create specific device message payloads | -| `OutputPulseEnabled` | Input | Specifies the value that enables the pulse function for the specified output lines | -| `PulseDO2` | Input | Specifies the value that is the duration of the output pulse in milliseconds | -| `Amplitude` | Input | Sets the amplitude of the signal waveform | -| `BufferLength` | Input | Sets the number of samples in each output | -| `Depth` | Input | Sets the bit depth of each element in the output | -| `DepthSpecified` | Input | Gets a value indicating whether Depth should be serialised | -| `Frequency` | Input | Sets the frequency of the signal waveform in Hz | -| `Offset` | Input | Sets an optional DC-offset of the signal waveform | -| `Phase` | Input | Sets an optional phase offset of the signal waveform in radians | -| `SampleRate` | Input | Sets the sampling rate of the generated signal waveform in Hz | -| `Waveform` | Input | Sets a value specifying the periodic waveform used to sample the signal | -| `BufferSize` | Input | Sets the number of samples to generate for finite samples, or size of buffer for continuous samples | -| `Channels` | Input | Gets the collection of analog output channels used to generate voltage signals | -| `SampleMode` | Input | Sets a value specifying whether a finite number of samples is generated or a continuous generation | -| `ColumnTiles` | Input | Sets the desired number of times to repeat each array in the horizontal dimension | -| `RowTiles` | Input | Sets the desired number of times to repeat each array in the horizontal dimension | -| `Depth` | Input | Sets the optional bit depth of each element in the output array | -| `Scale` | Input | Sets the optional scale factor to apply to array elements | -| `Shift` | Input | Sets the optional value to be added to each array element | -| `DeviceName` | Input | Sets the device that should be used to implement audio processing | -| `HostApi` | Input | Sets the host API that should be used to implement audio processing | -| `SampleRate` | Input | Sets the desired sample rate of the output stream in seconds | -| `SuggestedLatency` | Input | Sets the desired latency of output in seconds | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. - -[Bonsai.Harp] (https://www.nuget.org/packages/Bonsai.Harp) -[Harp.Behavior] (https://www.nuget.org/packages/Harp.Behavior) -[Bonsai.Mixer] (https://www.nuget.org/packages/Bonsai.Mixer) -[Bonsai.Dsp] (https://www.nuget.org/packages/Bonsai.Dsp) -[Bonsai.Dsp.Design] (https://www.nuget.org/packages/Bonsai.Dsp.Design) -[Aeon.Acquisition] (https://www.nuget.org/packages/Aeon.Acquisition) \ No newline at end of file diff --git a/utilities/Speaker-Playback/SpeakerPlayback.bonsai b/utilities/Speaker-Playback/SpeakerPlayback.bonsai deleted file mode 100644 index 86450d2..0000000 --- a/utilities/Speaker-Playback/SpeakerPlayback.bonsai +++ /dev/null @@ -1,424 +0,0 @@ - - - - - - - - - - - - BehaviorBoard - - - - - - - BehaviorCommands - - - - - - - - - - Active - On - true - On - Disabled - false - COM3 - - - - - - - BehaviorEvents - - - - - - BehaviorEvents - - - - - - - 10 - - - - - - - - 1 - - - - - - - Write - - DO2 - - - - - - - Write - - DO2 - - - - - - - Write - - 500 - - - - - - - - - - - - - - PT0.1S - - - - - - - BehaviorCommands - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - InitInputVariables - - - - - - - - 0 - - - - - - - SpeakerSignal - - - - - - - 0 - - - - - - - AuditoryStarted - - - - - - - - - - - - - - - - - InitMixer - - - - - - - - Windows WDM-KS - Speakers (XMOS xCORE-200 MC (UAC2.0)) - 44100 - - - - - - - - - - - - - - Mixer - - - - - - Buffer - - - - - - Mixer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PT0S - PT5S - - - - - - - - 1 - - - - - - - AuditoryStarted - - - - - - CreateAuditoryCue - - - - - - - Source1 - - - - - - - - - - - 1000 - 400 - Sine - 10000 - F32 - 1 - 0 - 0 - - - - - - - - 32 - 1 - - - - - - - - - 1 - 0 - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Buffer - - - - - - - PT0.1S - - - - - - - - 0 - - - - - - - AuditoryStarted - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utilities/Speaker-Playback/image.png b/utilities/Speaker-Playback/image.png deleted file mode 100644 index 57ffad4..0000000 Binary files a/utilities/Speaker-Playback/image.png and /dev/null differ diff --git a/utilities/Utilities.json b/utilities/Utilities.json deleted file mode 100644 index 2d052b0..0000000 --- a/utilities/Utilities.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema", - "$id": "lab://schemas/UtilitiesConfig.json", - "title": "UtilitiesConfig", - "type": "object", - "description": "Unified configuration for lab utilities. Include only modules you use; arrays can be empty or omitted.", - "properties": { - "modules": { - "type": "object", - "description": "Modules used on a given rig or workflow. Each is an array so you can instantiate multiple (or zero) devices.", - "properties": { - "BehaviorBoard": { - "type": "array", - "items": { - "$ref": "#/$defs/BehaviorBoardConfig" - } - }, - "CameraTriggerController": { - "type": "array", - "items": { - "$ref": "#/$defs/CameraTriggerControllerConfig" - } - }, - "LicketySplit": { - "type": "array", - "items": { - "$ref": "#/$defs/LicketySplitConfig" - } - }, - "RunningWheel": { - "type": "array", - "items": { - "$ref": "#/$defs/RunningWheelConfig" - } - }, - "TriggeredCameraAcquisition": { - "type": "array", - "items": { - "$ref": "#/$defs/TriggeredCameraAcquisitionConfig" - } - } - } - } - }, - "$defs": { - "BehaviorBoardConfig": { - "type": "object", - "properties": { - "EventsSubjectName": { - "type": "string", - "description": "The name of the output \"Events\" Subject", - "default": "BehaviorEvents" - }, - "CommandsSubjectName": { - "type": "string", - "description": "The name of the input \"Commands\" Subject", - "default": "BehaviorCommands" - }, - "PortName": { - "type": "string", - "default": "COMx", - "description": "The name of the serial connection to communicate with this device" - }, - "DumpRegisters": { - "type": "boolean", - "default": true - }, - "Heartbeat": { - "type": "string", - "default": "Enabled" - } - }, - "required": [ - "EventsSubjectName", - "CommandsSubjectName", - "PortName", - "DumpRegisters", - "Heartbeat" - ] - }, - "CameraTriggerControllerConfig": { - "type": "object", - "properties": { - "TriggerFrequency": { - "type": "number", - "description": "The frequency at which to trigger cameras.", - "default": 50 - }, - "BehaviorCommandsSubjectName": { - "type": "string", - "description": "The name of the subject that carries command messages to a specific behavior device.", - "default": "BehaviorCommands" - }, - "BehaviorEventsSubjectName": { - "type": "string", - "description": "The name of the subject that carries event messages from a specific behavior device.", - "default": "BehaviorEvents" - }, - "CameraTriggerEventsSubjectName": { - "type": "string", - "description": "The name of the subject to which timestamped camera trigger events from the behavior board are published.", - "default": "CameraTriggerEvents" - } - }, - "required": [ - "TriggerFrequency", - "BehaviorCommandsSubjectName", - "BehaviorEventsSubjectName", - "CameraTriggerEventsSubjectName" - ] - }, - "LicketySplitConfig": { - "type": "object", - "properties": { - "LickStateSubjectName": { - "type": "string", - "description": "The name of the subject that carries the state of the lick detector whenever it changes. HIGH when lick is detected, LOW when not.", - "default": "LickState" - }, - "CommandsSubjectName": { - "type": "string", - "description": "The name of the subject that carries command messages to a specific behavior device.", - "default": "LickCommands" - }, - "EventsSubjectName": { - "type": "string", - "description": "The name of the subject that carries event messages from a specific behavior device.", - "default": "LickEvents" - }, - "PortName": { - "type": "string", - "description": "The name of the serial connection to communicate with this device", - "default": "COMx" - }, - "DumpRegisters": { - "type": "boolean", - "default": true - }, - "Heartbeat": { - "type": "string", - "default": "Enabled" - }, - "Channel0TriggerThreshold": { - "type": "integer", - "default": 0 - }, - "Channel0UntriggerThreshold": { - "type": "integer", - "default": 0 - } - } - }, - "RunningWheelConfig": { - "type": "object", - "properties": { - "BehaviorEventsSubjectName": { - "type": "string", - "description": "The name of the Subject that carries event messages from a specific behavior device", - "default": "BehaviorEvents" - }, - "BehaviorCommandsSubjectName": { - "type": "string", - "description": "The name of the Subject that carries command messages to a specific behavior device", - "default": "BehaviorCommands" - }, - "StepsPerRev": { - "type": "integer", - "description": "The number of steps recorded per full revolution for this rotary encoder", - "default": 4096 - }, - "WheelDiameterMm": { - "type": "number", - "description": "The diameter of the running wheel in millimetres", - "default": 600 - } - } - }, - "TriggeredCameraAcquisitionConfig": { - "type": "object", - "properties": { - "ExposureTime": { - "type": "integer", - "default": 10000 - }, - "Gain": { - "type": "number", - "default": 1 - }, - "Binning": { - "type": "integer", - "default": 1 - }, - "SerialNumber": { - "type": "string", - "default": "1234567890" - }, - "ColorProcessing": { - "type": "string", - "default": "Default" - }, - "FrameEventsSubjectName": { - "type": "string", - "description": "The name of the subject to which indexed, timestamped camera frames are published", - "default": "FrameEvents" - } - }, - "required": [ - "ExposureTime", - "Gain", - "Binning", - "SerialNumber", - "ColorProcessing", - "FrameEventsSubjectName" - ] - } - } -} \ No newline at end of file diff --git a/utilities/Visual-Playback/README.md b/utilities/Visual-Playback/README.md deleted file mode 100644 index bfe9398..0000000 --- a/utilities/Visual-Playback/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Visual Playback Workflow - -### Purpose -This workflow initialises visual stimuli to be displayed on a hardware device of choice by receiving rendering commands from other workflows. These visual stimuli can be changed via multiple parameters to produce a stimulus of choice to be used in wider behavioural and experimental workflows. - -## Hardware Requirements -- Display device - -## Bonsai Workflow -This workflow sets up the graphics pipeline for visual stimulus presentation and allows for parameter settings to be changed. - -workflow - -## Properties -The properties of this workflow allow the user to configure the parameters needed to allow rendering of a visual stimulus of choice onto a display device - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** |**Description** | -|-------------------------|---------------------------------------------------------------------------------------------| -| `DrawStimuli` | Input | The name of the subject rendering commands and visual stimulus parameters | -| `CreateWindow` | Input | The name of the subject that creates the shader window to be displayed as a background for the stimulus. | -| `RenderFrame` | Input | The name of the subject that generates a sequence of events when it is time to render a new frame | -| `DrawStimuli` | Output | The name of the subject that publishes rendered context for visual stimulus presentation to other aspects within a workflow | - - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|-------------------------------------------------------------------------------------------------------| -| `Window Style` | Input | Takes in parameters to set window width, height,location, and border presence, cursor visibility and state of window | -| `Render Settings` | Input | Takes in parameters to set the colour to clear the buffer before rendering, which buffers to clear, which device to use, which graphics to use, the initial rendering state of the window, and window rendering and update frequencies | - - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [Bonsai.Shaders] (https://www.nuget.org/packages/Bonsai.Shaders) -- [Bonsai.Shaders.Design] (https://www.nuget.org/packages/Bonsai.Shaders.Design) -- [Bonvision](https://www.nuget.org/packages/BonVision) - diff --git a/utilities/Visual-Playback/VisualPlayback.bonsai b/utilities/Visual-Playback/VisualPlayback.bonsai deleted file mode 100644 index 117bbce..0000000 --- a/utilities/Visual-Playback/VisualPlayback.bonsai +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - 600 - 400 - On - false - Gray - DepthBufferBit ColorBufferBit - false - - Hidden - Normal - Second - 60 - - - - - 8 - 8 - 8 - 8 - - 16 - 0 - 0 - - 0 - 0 - 0 - 0 - - 2 - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DrawStimuli - - - - - - DrawStimuli - - - - - - 0.0174524058 - 0 - 0 - 1 - 1 - 20 - 0 - 0 - 0 - false - 0.2 - - 10 - 1 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utilities/Visual-Playback/image.png b/utilities/Visual-Playback/image.png deleted file mode 100644 index 8f50c87..0000000 Binary files a/utilities/Visual-Playback/image.png and /dev/null differ diff --git a/utilities/camera-acquisition/CameraAcquisition.bonsai b/utilities/camera-acquisition/CameraAcquisition.bonsai deleted file mode 100644 index 69dd9bd..0000000 --- a/utilities/camera-acquisition/CameraAcquisition.bonsai +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - Default - - - - Image - - - - - - - - - FrameEvents - - - - - - - - - - - - - \ No newline at end of file diff --git a/utilities/camera-acquisition/README.md b/utilities/camera-acquisition/README.md deleted file mode 100644 index 2d021b5..0000000 --- a/utilities/camera-acquisition/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Camera Aqcuisition Workflow - -### Purpose -The purpose of this workflow is to acquire frames from FLIR cameras without an external trigger. - -## Hardware Requirements -- FLIR camera - -## Bonsai Workflow -This workflow acquires frames from FLIR cameras. - -workflow - -## Properties -The properties of this workflow allow the user to specify the serial number of their FLIR camera. This is the 8-digit code located on the bottom of the camera. However, the SpinnakerCapture node should automatically detect the camera and serial number. - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `FrameEventsSubjectName` | Output | The name of the Subject to which acquired camera frames will be published | -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `SerialNumber` | Input | Serial number of the FLIR camera | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [Spinnaker](https://www.nuget.org/packages/Bonsai.Spinnaker) - - Please download v0.7.1, as the new version of the bonsai package is broken. diff --git a/utilities/camera-acquisition/assets/CameraAcquisition.svg b/utilities/camera-acquisition/assets/CameraAcquisition.svg deleted file mode 100644 index 437d497..0000000 --- a/utilities/camera-acquisition/assets/CameraAcquisition.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]>WorkflowOutputFrameEventsGrayscaleFrameEventsSubjectNameImageSpinnakerCaptureSerialNumber \ No newline at end of file diff --git a/utilities/device-lickety-split/README.md b/utilities/device-lickety-split/README.md deleted file mode 100644 index 212eb66..0000000 --- a/utilities/device-lickety-split/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# LicketySplit interface - -This module interfaces and configures a [LicketySplit](https://allenneuraldynamics.github.io/Bonsai.AllenNeuralDynamics/harp_devices_spec/Harp_LicketySplit.html) device by Allen Neural Dynamics: - -LicketySplit - -## Bonsai Workflow - -workflow - -## Properties -The properties of this workflow allow the user to configure device parameters and set the name of shared `Subjects` to either publish or subscribe to as an interface between your broader Bonsai workflow and the device. -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-----------------------------|-----------------------|-----------------------------------------------------------------------------| -| `CommandsSubjectName` | Input | The name of the input subject that sends commands to the device. | -| `EventsSubjectName` | Output | The name of the subject that receives event messages from the device. | -| `LickStateSubjectName` | Output | The name of the subject that indicates the lick detector state (HIGH/LOW). | -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-----------------------------|-----------------------|-----------------------------------------------------------------------------| -| `PortName` | Input | Serial port used to communicate with the device (e.g., COM4). | -| `DumpRegisters` | Input | Whether to dump register values from the device at startup. | -| `Heartbeat` | Input | Whether to enable heartbeat messages from the device. | -| `Channel0TriggerThreshold` | Input | The voltage or signal threshold to detect a lick (rising edge). | -| `Channel0UntriggerThreshold`| Input | The threshold to detect when a lick ends (falling edge). | - -## Dependencies -### Bonsai: -[AllenNeuralDynamics.HarpUtils](https://www.nuget.org/packages/AllenNeuralDynamics.HarpUtils/) diff --git a/utilities/device-lickety-split/assets/LicketySplit.svg b/utilities/device-lickety-split/assets/LicketySplit.svg deleted file mode 100644 index ba921a8..0000000 --- a/utilities/device-lickety-split/assets/LicketySplit.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]>LickEventsLickCommandsLickStateLicketySplitMergeLicketySplit.LickStateLickStateSubjectNameLickCommandsPortName,DumpRegisters,HeartLicketySplit.Channel0UntriggerThresLickEventsCommandsSubjectNameLicketySplit.Channel0TriggerThreshChannel0UntriggerThresholdTakeChannel0TriggerThresholdLickEventsEventsSubjectName \ No newline at end of file diff --git a/utilities/device-lickety-split/assets/image.png b/utilities/device-lickety-split/assets/image.png deleted file mode 100644 index 3b91127..0000000 Binary files a/utilities/device-lickety-split/assets/image.png and /dev/null differ diff --git a/utilities/running-wheel/README.md b/utilities/running-wheel/README.md deleted file mode 100644 index c68ff6a..0000000 --- a/utilities/running-wheel/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Running Wheel Workflow - -### Purpose -The purpose of this workflow is to track the total distance moved by a running wheel. - -## Bonsai Workflow -When this workflow first receives events from a [HARP behavior board](https://github.com/harp-tech/device.behavior) connected to a quadrature encoder, it sends commands to enable the encoder and resets its count to zero. - -Incoming device events are then filtered for analog inputs, and the quadrature encoder output is extracted. The encoder ticks are then converted into distance (in centimeters) using the user-defined encoder resolution (counts per revolution) and wheel diameter. - -The resulting distance in centimeters is the final output of the workflow. - -workflow - -## Properties -The properties of this workflow allow the user to define the encoder resolution (counts per revolution) and the running wheel diameter (in millimeters). These values are used to convert the encoder tick count into running distance (in centimeters). Note that the Kubler 05.2400.1122.1024 quadrature encoder produces 1024 counts per revolution, but the HARP board’s built-in quadrature counter uses x4 decoding. This means it counts all four edges of the A/B signals (rising and falling edges of both channels), giving an effective resolution of 4096 counts per revolution. - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `EventsSubjectName` | Input | The name of the subject that receives event messages from the device. | -| `CommandsSubjectName` | Output | The name of the subject that sends commands to the device. | -| `WheelDistance_cm` | Output | The total distance the wheel has moved (in centimeters). | - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `CountsPerRev` | Input | The tick counts per revolution of the wheel. | -| `WheelDiameter_mm` | Input | The wheel diameter (in millimeters). | - -## Dependencies -### Bonsai: -In order to use this module, bonsai must have the following modules installed via the package manager or `Bonsai.config` file. -- [Harp.Behavior](https://www.nuget.org/packages/Harp.Behavior) \ No newline at end of file diff --git a/utilities/running-wheel/assets/RunningWheel.svg b/utilities/running-wheel/assets/RunningWheel.svg deleted file mode 100644 index 2f0b127..0000000 --- a/utilities/running-wheel/assets/RunningWheel.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]>SubscribeWhenWorkflowOutputBehaviorCommandsBehaviorEventsWheelDistance_cmMergeCommandsSubjectNameTicksToCentimetersBehavior.EncoderResetPayloadBehavior.EnableEncodersPayloEncoderCountsPerRev,WheelDiameter_mmBehavior.AnalogDataBehaviorEventsEventsSubjectName \ No newline at end of file diff --git a/utilities/running-wheel/running-wheel.png b/utilities/running-wheel/running-wheel.png deleted file mode 100644 index f62e6ab..0000000 Binary files a/utilities/running-wheel/running-wheel.png and /dev/null differ diff --git a/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.editor b/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.editor deleted file mode 100644 index bbd6dca..0000000 --- a/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.editor +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.layout b/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.layout deleted file mode 100644 index 0829d87..0000000 --- a/utilities/sleap-tracking/.bonsai/Settings/sleap-tracking.layout +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/utilities/sleap-tracking/Extensions.csproj b/utilities/sleap-tracking/Extensions.csproj deleted file mode 100644 index ba07f94..0000000 --- a/utilities/sleap-tracking/Extensions.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net472 - - - - - - - - - \ No newline at end of file diff --git a/utilities/sleap-tracking/Extensions/ExtractBodyPartPositions.cs b/utilities/sleap-tracking/Extensions/ExtractBodyPartPositions.cs deleted file mode 100644 index 5b44375..0000000 --- a/utilities/sleap-tracking/Extensions/ExtractBodyPartPositions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Bonsai; -using System; -using System.ComponentModel; -using System.Reactive.Linq; -using System.Collections.Generic; -using Bonsai.Sleap; -using Bonsai.Reactive; -using OpenCV.Net; // bc ElementIndex ouputs an OpenCV.Net.IplImage when indexing video frames - -public class BodyPart -{ - public int Frame { get; set; } - public string PartName { get; set; } - public float X { get; set; } - public float Y { get; set; } - public float Confidence { get; set; } - - public override string ToString() // override default ToString() behavior to display BodyPart information in the Bonsai TextVisualiser - { - return "Frame=" + Frame + - " PartName=" + PartName + - " X=" + X.ToString("F2") + - " Y=" + Y.ToString("F2") + - " Conf=" + Confidence.ToString("F2"); - } -} - -[Combinator] -[Description("Takes as input a zipped Pose and ElementIndex and outputs (Frame, PartName, X, Y, Confidence) for each bodyPart in the Pose.")] -[WorkflowElementCategory(ElementCategory.Transform)] -public class ExtractBodyPartPositions -{ - public IObservable Process( - IObservable, Pose>> source) - { - return source.SelectMany(items => - { - int frame = items.Item1.Index; - Pose pose = items.Item2; - - var bodyPartPositions = new List(pose.Count); - foreach (var bodyPart in pose) - { - bodyPartPositions.Add(new BodyPart - { - Frame = frame, - PartName = bodyPart.Name, - X = bodyPart.Position.X, - Y = bodyPart.Position.Y, - Confidence = bodyPart.Confidence - }); - } - return bodyPartPositions; - }); - } -} \ No newline at end of file diff --git a/utilities/sleap-tracking/README.md b/utilities/sleap-tracking/README.md deleted file mode 100644 index 37796e4..0000000 --- a/utilities/sleap-tracking/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Live Sleap Tracking Workflow - -### Purpose -The purpose of this workflow is to do live Sleap tracking of animal poses. - -## Bonsai Workflow -This workflow accepts a pre-trained Sleap model and a video stream as input and ouputs the frame, body part name, x position, y position, and confidence of each model body part for each frame. - -workflow - -## Properties -The properties of this workflow allow the user to provide the path to their pre-trained model and config files. - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `FrameEventsSubjectName`| Input | The name of the subject that receives acquired camera frames. | -| `BodyPartPositionsSubjectName`| Output | The name of the subject to which body part information will be published. | - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|---------------------------------------------------------------------------| -| `ModelFileName` | Input | The path to the frozen_graph.pb file. | -| `TrainingConfig` | Input | The path to the training config JSON file. | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [Sleap](https://www.nuget.org/packages/Bonsai.Sleap) -- [Sleap.Design](https://www.nuget.org/packages/Bonsai.Sleap.Design) \ No newline at end of file diff --git a/utilities/sleap-tracking/sleap-tracking.bonsai b/utilities/sleap-tracking/sleap-tracking.bonsai deleted file mode 100644 index a70c010..0000000 --- a/utilities/sleap-tracking/sleap-tracking.bonsai +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - FrameEvents - - - - - - - - - - - C:\Users\CoenL\Documents\TomC\sleap\Models\QW001_2024-11-26_01_video1\frozen_graph.pb - C:\Users\CoenL\Documents\TomC\sleap\Models\QW001_2024-11-26_01_video1\training_config.json - - - - - - - - - - - - - - - - BodyPartPositions - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utilities/sleap-tracking/sleap-tracking.png b/utilities/sleap-tracking/sleap-tracking.png deleted file mode 100644 index eb6b649..0000000 Binary files a/utilities/sleap-tracking/sleap-tracking.png and /dev/null differ diff --git a/utilities/triggered-camera-acquisition/README.md b/utilities/triggered-camera-acquisition/README.md deleted file mode 100644 index 2e86cc9..0000000 --- a/utilities/triggered-camera-acquisition/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Camera Trigger Workflow - -### Purpose -This workflow exists to timestamp and index triggered camera frames from a Spinnaker camera in line with timestamped triggers originating from a behavior board or camera controller device. - -## Hardware Requirements -- Spinnaker camera -- BehaviourBoard - -## Bonsai Workflow -This workflow aligns camera frames triggered from an external device in time for usage by broader behavioural & experimental workflows, and analysis. - -workflow - -## Properties -The properties of this workflow allow the user to configure the parameters needed to allow communication between Spinnaker cameras, the BehaviorBoard, other Bonsai workflows and other Harp devices - - -### Input and Output `Subjects` -| **Property Name** | **Input/Output** |**Description** | -|-------------------------|---------------------------------------------------------------------------------------------| -| `CameraTriggerEventsSubjectName` | Input | The name of the input subject that receives frame event data from the BehaviorBoard. **N.B. This must match `CameraTriggerEventsSubjectName` in your `CameraTriggerController` module.** | -| `FrameEventsSubjectName` | Output | The name of the subject that receives timestamped and indexed camera event synchronised with trigger event messages from the device. | - -### Configuration Parameters -| **Property Name** | **Input/Output** | **Description** | -|-------------------------|------------------|-------------------------------------------------------------------------------------------------------| -| `ExposureTime` | Input | Camera exposure duration in microseconds | -| `Gain` | Input | Sets the sensor gain multiplier | -| `Binning` | Input | Sets the pixel binning factor for setting sensitivity | -| `SerialNumber` | Input | Sets serial number of Spinnaker cameras | -| `ColorProcessing` | Input | Sets mode for color processing | -| `TriggerEvents` | Output | Name of the SubscribeSubject node for output to wider workflow that CameraTriggerEvents subscribes to | - -## Dependencies -### Bonsai: -In order to use this module, Bonsai must have the following modules installed via the package manager or 'Bonsai.config' file. -- [Bonsai.Spinnaker](https://www.nuget.org/packages/Bonsai.Spinnaker) -- [Bonsai.Harp](https://www.nuget.org/packages/Bonsai.Harp) -- [Behaviour Board utility](https://github.com/Coen-Lab/bonsai-workflows/tree/main/utilities/device-behavior-board) -- [Behaviour Board Camera Trigger Controller utility](https://github.com/Coen-Lab/bonsai-workflows/tree/main/utilities/device-behavior-board/add.cameraTriggerController.bonsai) - -### External Software: -- [Spinnaker SDK 1.29.0.5] (https://www.teledynevisionsolutions.com/en-GB/products/spinnaker-sdk/?model=Spinnaker%20SDK&vertical=machine%20vision&segment=iis) - -Note: use this version of Spinnaker with Bonsai Spinnaker version 0.7.1 for compatibility. \ No newline at end of file diff --git a/utilities/triggered-camera-acquisition/assets/TriggeredCameraAcquisition.svg b/utilities/triggered-camera-acquisition/assets/TriggeredCameraAcquisition.svg deleted file mode 100644 index b188181..0000000 --- a/utilities/triggered-camera-acquisition/assets/TriggeredCameraAcquisition.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]>WorkflowOutputFrameEventsCreateTimestampedFrameEventsSubjectNameAnnotationZipAnnotationElementIndexSecondsAnnotationSpinnakerCaptureAnnotationSkipAnnotationExposureTime,Gain,Binning,SerialAnnotationCameraTriggerEventsAnnotationCameraTriggerEventsSubjectNameAnnotation \ No newline at end of file