├── .bumpversion.cfg ├── .ci └── build.cmd ├── .clang-format ├── .coveragerc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── auto_assign.yml ├── dependabot.yml ├── milestones.py ├── stale.yml └── workflows │ ├── docs_test_action.yml │ ├── flake8.yml │ ├── manylinux_build.yml │ ├── mypy.yml │ ├── octocheese.yml │ ├── python_ci.yml │ └── python_ci_linux.yml ├── .gitignore ├── .imgbotconfig ├── .pre-commit-config.yaml ├── .pylintrc ├── .readthedocs.yml ├── .style.yapf ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── MoNA_GCMS_Library ├── AUTHORS ├── MoNA-export-GC-MS_Spectra-json.zip ├── MoNA.msp ├── MoNA │ ├── ALNUMNAM.INU │ ├── ALPHANAM.INU │ ├── FORM.INU │ ├── LOSS.DBU │ ├── LOSS.INU │ ├── MAXMASS.DBU │ ├── MAXMASS.INU │ ├── MW.INU │ ├── NAME.INU │ ├── NAMESORT.DBU │ ├── PEAK.DBU │ ├── PEAK.INU │ ├── PEAK_AM0.DBU │ ├── PEAK_AM0.INU │ ├── PEAK_AM2.DBU │ ├── PEAK_AM2.INU │ ├── REGISTRY.INU │ ├── USER.DBU │ ├── USER.INU │ └── USERID.NUM ├── __init__.py ├── parse_json_and_contribs.py ├── parse_mona_contributors.py └── parse_mona_json.py ├── NISTDLL Reference.pdf ├── README.rst ├── THIRD_PARTY_COPYRIGHT ├── appveyor.yml ├── cleanup.sh ├── copr_header.txt ├── coverage-fixup.py ├── doc-source ├── 404.rst ├── Source.rst ├── _static │ └── style.css ├── _templates │ └── base.html ├── api.rst ├── conf.py ├── contributing.rst ├── docutils.conf ├── git_download.png ├── index.rst ├── license.rst ├── not-found.png ├── requirements.txt └── usage.rst ├── formate.toml ├── justfile ├── pyproject.toml ├── read_jcamp.py ├── repo_helper.yml ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── pyms_nist_search │ ├── NISTERR.H │ ├── NISTMS.H │ ├── NISTMS_min.H │ ├── __init__.py │ ├── __init__.pyi │ ├── base.py │ ├── docker_engine.py │ ├── mona_tools.py │ ├── py.typed │ ├── pyms_nist_search.c │ ├── pyms_nist_search_min.c │ ├── reference_data.py │ ├── search_result.py │ ├── templates │ ├── __init__.py │ └── msp_template │ ├── utils.py │ ├── win_engine.py │ ├── x64 │ ├── ctnt66_64.dll │ ├── nistdl64.dll │ ├── nistdl64.lib │ ├── nistdl64_2gb.dll │ └── nistdl64_2gb.lib │ └── x86 │ ├── ctnt66.dll │ ├── nistdl32.dll │ ├── nistdl32.lib │ ├── nistdl32_2gb.dll │ └── nistdl32_2gb.lib ├── stubs.txt ├── test.py ├── tests ├── __init__.py ├── conftest.py ├── constants.py ├── engines.py ├── requirements.txt ├── spectra.py ├── test_full_search.py ├── test_full_search_ref_data.py ├── test_get_ref_data.py ├── test_quick_search.py ├── test_reference_data.py ├── test_search_result.py └── test_utils.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.7.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:src/pyms_nist_search/__init__.py] 9 | search = : str = "{current_version}" 10 | replace = : str = "{new_version}" 11 | 12 | [bumpversion:file:README.rst] 13 | 14 | [bumpversion:file:doc-source/index.rst] 15 | 16 | [bumpversion:file:repo_helper.yml] 17 | 18 | [bumpversion:file:pyproject.toml] 19 | search = version = "{current_version}" 20 | replace = version = "{new_version}" 21 | 22 | [bumpversion:file:setup.cfg] 23 | search = version = {current_version} 24 | replace = version = {new_version} 25 | -------------------------------------------------------------------------------- /.ci/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: To build extensions for 64 bit Python 3, we need to configure environment 3 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 4 | :: MS Windows SDK for Windows 7 and .NET Framework 4 5 | :: 6 | :: More details at: 7 | :: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows 8 | 9 | IF "%DISTUTILS_USE_SDK%"=="1" ( 10 | ECHO Configuring environment to build with MSVC on a 64bit architecture 11 | ECHO Using Windows SDK 7.1 12 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 13 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 14 | SET MSSdk=1 15 | REM Need the following to allow tox to see the SDK compiler 16 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 17 | ) ELSE ( 18 | ECHO Using default MSVC build environment 19 | ) 20 | 21 | CALL %* 22 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | TabWidth: 4 4 | UseTab: Always 5 | ColumnLimit: 120 6 | IndentPPDirectives: BeforeHash 7 | Cpp11BracedListStyle: false 8 | IncludeBlocks: Preserve 9 | SortIncludes: Never 10 | IndentCaseLabels: true 11 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | relative_files = True 3 | 4 | [paths] 5 | pyms_nist_search = 6 | src/pyms_nist_search/ 7 | .tox/py3*/lib/python3*/site-packages/pyms_nist_search/ 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | assignees: domdfcoding 6 | 7 | --- 8 | 9 | 14 | 15 | ## Description 16 | 17 | 18 | 19 | ## Steps to Reproduce 20 | 24 | 25 | 1. 26 | 2. 27 | 3. 28 | 29 | ## Actual result: 30 | 31 | 32 | 33 | ## Expected result: 34 | 35 | 36 | ## Reproduces how often: 37 | 38 | 39 | 40 | ## Version 41 | 42 | * Operating System: 43 | * Python: 44 | * pyms-nist-search: 45 | 46 | ## Installation source 47 | 48 | 49 | 50 | ## Other Additional Information: 51 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: "enhancement" 5 | assignees: domdfcoding 6 | 7 | --- 8 | 9 | 11 | 12 | 13 | ## Description 14 | 15 | 16 | 17 | 18 | 19 | ## Version 20 | 21 | * Operating System: 22 | * Python: 23 | * pyms-nist-search: 24 | 25 | 26 | ## Other Additional Information: 27 | 28 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | addReviewers: true 4 | addAssignees: true 5 | reviewers: 6 | - domdfcoding 7 | numberOfReviewers: 0 8 | 9 | # more settings at https://github.com/marketplace/actions/auto-assign-action 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | version: 2 4 | updates: 5 | - package-ecosystem: pip 6 | directory: / 7 | schedule: 8 | interval: weekly 9 | open-pull-requests-limit: 0 10 | reviewers: 11 | - domdfcoding 12 | -------------------------------------------------------------------------------- /.github/milestones.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # stdlib 4 | import os 5 | import sys 6 | 7 | # 3rd party 8 | from github3 import GitHub 9 | from github3.repos import Repository 10 | from packaging.version import InvalidVersion, Version 11 | 12 | latest_tag = os.environ["GITHUB_REF_NAME"] 13 | 14 | try: 15 | current_version = Version(latest_tag) 16 | except InvalidVersion: 17 | sys.exit() 18 | 19 | gh: GitHub = GitHub(token=os.environ["GITHUB_TOKEN"]) 20 | repo: Repository = gh.repository(*os.environ["GITHUB_REPOSITORY"].split('/', 1)) 21 | 22 | for milestone in repo.milestones(state="open"): 23 | try: 24 | milestone_version = Version(milestone.title) 25 | except InvalidVersion: 26 | continue 27 | if milestone_version == current_version: 28 | sys.exit(not milestone.update(state="closed")) 29 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | # Configuration for probot-stale - https://github.com/probot/stale 3 | --- 4 | 5 | # Number of days of inactivity before an Issue or Pull Request becomes stale 6 | daysUntilStale: 180 7 | 8 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 9 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 10 | daysUntilClose: false 11 | 12 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 13 | onlyLabels: [] 14 | 15 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 16 | exemptLabels: 17 | - pinned 18 | - security 19 | - "[Status] Maybe Later" 20 | 21 | # Set to true to ignore issues in a project (defaults to false) 22 | exemptProjects: false 23 | 24 | # Set to true to ignore issues in a milestone (defaults to false) 25 | exemptMilestones: false 26 | 27 | # Set to true to ignore issues with an assignee (defaults to false) 28 | exemptAssignees: false 29 | 30 | # Label to use when marking as stale 31 | staleLabel: stale 32 | 33 | # Comment to post when marking as stale. Set to `false` to disable 34 | markComment: false 35 | # This issue has been automatically marked as stale because it has not had 36 | # recent activity. It will be closed if no further activity occurs. Thank you 37 | # for your contributions. 38 | 39 | # Comment to post when removing the stale label. 40 | # unmarkComment: > 41 | # Your comment here. 42 | 43 | # Comment to post when closing a stale Issue or Pull Request. 44 | # closeComment: > 45 | # Your comment here. 46 | 47 | # Limit the number of actions per hour, from 1-30. Default is 30 48 | limitPerRun: 30 49 | 50 | # Limit to only `issues` or `pulls` 51 | # only: issues 52 | 53 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 54 | # pulls: 55 | # daysUntilStale: 30 56 | # markComment: > 57 | # This pull request has been automatically marked as stale because it has not had 58 | # recent activity. It will be closed if no further activity occurs. Thank you 59 | # for your contributions. 60 | 61 | # issues: 62 | # exemptLabels: 63 | # - confirmed 64 | -------------------------------------------------------------------------------- /.github/workflows/docs_test_action.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: "Docs Check" 4 | on: 5 | push: 6 | branches-ignore: 7 | - 'repo-helper-update' 8 | - 'pre-commit-ci-update-config' 9 | - 'imgbot' 10 | pull_request: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | docs: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 🛎️ 20 | uses: "actions/checkout@v4" 21 | 22 | - name: Check for changed files 23 | uses: dorny/paths-filter@v2 24 | id: changes 25 | with: 26 | list-files: "json" 27 | filters: | 28 | code: 29 | - '!tests/**' 30 | 31 | - name: Install and Build 🔧 32 | uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1 33 | if: steps.changes.outputs.code == 'true' 34 | with: 35 | pre-build-command: python -m pip install tox 36 | docs-folder: "doc-source/" 37 | build-command: "tox -e docs -- " 38 | -------------------------------------------------------------------------------- /.github/workflows/flake8.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: Flake8 4 | 5 | on: 6 | push: 7 | branches-ignore: 8 | - 'repo-helper-update' 9 | - 'pre-commit-ci-update-config' 10 | - 'imgbot' 11 | pull_request: 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | Run: 18 | name: "Flake8" 19 | runs-on: "ubuntu-22.04" 20 | 21 | steps: 22 | - name: Checkout 🛎️ 23 | uses: "actions/checkout@v4" 24 | 25 | - name: Check for changed files 26 | uses: dorny/paths-filter@v2 27 | id: changes 28 | with: 29 | list-files: "json" 30 | filters: | 31 | code: 32 | - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' 33 | 34 | - name: Setup Python 🐍 35 | if: steps.changes.outputs.code == 'true' 36 | uses: "actions/setup-python@v5" 37 | with: 38 | python-version: "3.8" 39 | 40 | - name: Install dependencies 🔧 41 | if: steps.changes.outputs.code == 'true' 42 | run: | 43 | python -VV 44 | python -m site 45 | python -m pip install --upgrade pip setuptools wheel 46 | python -m pip install tox~=3.0 47 | 48 | - name: "Run Flake8" 49 | if: steps.changes.outputs.code == 'true' 50 | run: "python -m tox -e lint -s false -- --format github" 51 | -------------------------------------------------------------------------------- /.github/workflows/manylinux_build.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: Build manylinux Wheels 4 | 5 | on: 6 | push: 7 | branches-ignore: 8 | - 'repo-helper-update' 9 | - 'pre-commit-ci-update-config' 10 | - 'imgbot' 11 | tags: 12 | - '*' 13 | pull_request: 14 | 15 | permissions: 16 | actions: write 17 | issues: write 18 | contents: read 19 | 20 | jobs: 21 | build: 22 | name: "manylinux-${{ matrix.config.tag }}" 23 | runs-on: "ubuntu-22.04" 24 | 25 | strategy: 26 | fail-fast: False 27 | matrix: 28 | config: 29 | - {python-version: "3.7", testenv: "py37", tag: "cp37-cp37m"} 30 | - {python-version: "3.8", testenv: "py38", tag: "cp38-cp38"} 31 | - {python-version: "3.9", testenv: "py39", tag: "cp39-cp39"} 32 | - {python-version: "3.10", testenv: "py310", tag: "cp310-cp310"} 33 | - {python-version: "3.11", testenv: "py311", tag: "cp311-cp311"} 34 | - {python-version: "3.12", testenv: "py312", tag: "cp312-cp312"} 35 | 36 | steps: 37 | - name: Checkout 🛎️ 38 | uses: "actions/checkout@v4" 39 | 40 | - name: Set up Python 🐍 41 | uses: "actions/setup-python@v5" 42 | with: 43 | python-version: "${{ matrix.config.python-version }}" 44 | 45 | - name: Install dependencies 🔧 46 | run: | 47 | python -VV 48 | python -m site 49 | python -m pip install --upgrade pip setuptools wheel 50 | python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 51 | sudo add-apt-repository universe 52 | sudo apt update 53 | sudo apt install libhdf5-dev netcdf-bin libnetcdf-dev 54 | 55 | - name: Build manylinux2010 Python wheels 🎡 56 | uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux2010_x86_64 57 | if: ${{ contains(fromJson('["3.11", "3.12"]'), matrix.config.python-version) == false }} 58 | with: 59 | python-versions: '${{ matrix.config.tag }}' 60 | package-path: '' 61 | pip-wheel-args: '--no-deps -w ./wheelhouse' 62 | 63 | - name: Build manylinux2014 Python wheels 🎡 64 | uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux2014_x86_64 65 | with: 66 | python-versions: '${{ matrix.config.tag }}' 67 | package-path: '' 68 | pip-wheel-args: '--no-deps -w ./wheelhouse' 69 | 70 | - name: Build manylinux_2_24 Python wheels 🎡 71 | uses: RalfG/python-wheels-manylinux-build@v0.7.1-manylinux_2_24_x86_64 72 | if: ${{ contains(fromJson('["3.12"]'), matrix.config.python-version) == false }} 73 | with: 74 | python-versions: '${{ matrix.config.tag }}' 75 | package-path: '' 76 | pip-wheel-args: '--no-deps -w ./wheelhouse' 77 | 78 | - name: Upload Artifacts 🚀 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: "wheels-${{ matrix.config.tag }}" 82 | path: wheelhouse/ 83 | 84 | - name: "Run Tests" 85 | run: | 86 | sudo rm wheelhouse/pyms_nist_search-*-${{ matrix.config.tag }}*-linux*.whl 87 | for whl in wheelhouse/pyms_nist_search-*-${{ matrix.config.tag }}*-manylinux*.whl; do 88 | # Test tox with wheels 89 | python -m tox -r -e ${{ matrix.config.testenv }} --installpkg "$whl" 90 | # TODO: Upload coverage to coveralls 91 | done 92 | 93 | - name: Upload distribution to PyPI 🚀 94 | if: startsWith(github.ref, 'refs/tags/') 95 | uses: pypa/gh-action-pypi-publish@v1.4.2 96 | with: 97 | user: __token__ 98 | password: ${{ secrets.PYPI_TOKEN }} 99 | packages_dir: wheelhouse/ 100 | skip_existing: true 101 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: mypy 4 | 5 | on: 6 | push: 7 | branches-ignore: 8 | - 'repo-helper-update' 9 | - 'pre-commit-ci-update-config' 10 | - 'imgbot' 11 | pull_request: 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | Run: 18 | name: "mypy / ${{ matrix.os }}" 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: ['ubuntu-22.04', 'windows-2019'] 24 | fail-fast: false 25 | 26 | steps: 27 | - name: Checkout 🛎️ 28 | uses: "actions/checkout@v4" 29 | 30 | - name: Check for changed files 31 | uses: dorny/paths-filter@v2 32 | id: changes 33 | with: 34 | list-files: "json" 35 | filters: | 36 | code: 37 | - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' 38 | 39 | - name: Setup Python 🐍 40 | if: steps.changes.outputs.code == 'true' 41 | uses: "actions/setup-python@v5" 42 | with: 43 | python-version: "3.8" 44 | 45 | - name: Install dependencies (Linux) 🔧 46 | if: ${{ matrix.os == 'ubuntu-22.04' && steps.changes.outputs.code == 'true' }} 47 | run: | 48 | python -VV 49 | python -m site 50 | python -m pip install --upgrade pip setuptools wheel 51 | python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 52 | sudo add-apt-repository universe 53 | sudo apt update 54 | sudo apt install libhdf5-dev netcdf-bin libnetcdf-dev 55 | 56 | - name: Install dependencies (Windows) 🔧 57 | if: ${{ matrix.os != 'ubuntu-22.04' && steps.changes.outputs.code == 'true' }} 58 | run: | 59 | python -VV 60 | python -m site 61 | python -m pip install --upgrade pip setuptools wheel 62 | python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 63 | pip config set global.prefer-binary true 64 | 65 | - name: "Run mypy" 66 | if: steps.changes.outputs.code == 'true' 67 | run: "python -m tox -e mypy -s false" 68 | -------------------------------------------------------------------------------- /.github/workflows/octocheese.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | 4 | name: "GitHub Releases" 5 | on: 6 | schedule: 7 | - cron: 0 12 * * * 8 | 9 | jobs: 10 | Run: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: domdfcoding/octocheese@master 14 | with: 15 | pypi_name: "pyms-nist-search" 16 | env: 17 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 18 | if: startsWith(github.ref, 'refs/tags/') != true 19 | -------------------------------------------------------------------------------- /.github/workflows/python_ci.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: Windows 4 | 5 | on: 6 | push: 7 | branches-ignore: 8 | - 'repo-helper-update' 9 | - 'pre-commit-ci-update-config' 10 | - 'imgbot' 11 | 12 | pull_request: 13 | 14 | permissions: 15 | actions: write 16 | issues: write 17 | contents: read 18 | 19 | jobs: 20 | tests: 21 | name: "windows-2019 / Python ${{ matrix.config.python-version }}" 22 | runs-on: "windows-2019" 23 | continue-on-error: ${{ matrix.config.experimental }} 24 | env: 25 | USING_COVERAGE: '3.7,3.9,3.10,3.11,3.12' 26 | 27 | strategy: 28 | fail-fast: False 29 | matrix: 30 | config: 31 | - {python-version: "3.7", testenvs: "py37,build", experimental: False} 32 | - {python-version: "3.8", testenvs: "py38,build", experimental: False} 33 | - {python-version: "3.9", testenvs: "py39,build", experimental: False} 34 | - {python-version: "3.10", testenvs: "py310,build", experimental: False} 35 | - {python-version: "3.11", testenvs: "py311,build", experimental: False} 36 | - {python-version: "3.12", testenvs: "py312,build", experimental: False} 37 | 38 | steps: 39 | - name: Checkout 🛎️ 40 | uses: "actions/checkout@v4" 41 | 42 | - name: Check for changed files 43 | if: startsWith(github.ref, 'refs/tags/') != true 44 | uses: dorny/paths-filter@v2 45 | id: changes 46 | with: 47 | list-files: "json" 48 | filters: | 49 | code: 50 | - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' 51 | 52 | - name: Setup Python 🐍 53 | id: setup-python 54 | if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} 55 | uses: "actions/setup-python@v5" 56 | with: 57 | python-version: "${{ matrix.config.python-version }}" 58 | 59 | - name: Install dependencies 🔧 60 | if: steps.setup-python.outcome == 'success' 61 | run: | 62 | python -VV 63 | python -m site 64 | python -m pip install --upgrade pip setuptools wheel 65 | python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 66 | pip config set global.prefer-binary true 67 | 68 | - name: "Run Tests for Python ${{ matrix.config.python-version }}" 69 | if: steps.setup-python.outcome == 'success' 70 | run: python -m tox -e "${{ matrix.config.testenvs }}" -s false 71 | 72 | - name: "Upload Coverage 🚀" 73 | uses: actions/upload-artifact@v4 74 | if: ${{ always() && steps.setup-python.outcome == 'success' }} 75 | with: 76 | name: "coverage-${{ matrix.config.python-version }}" 77 | path: .coverage 78 | include-hidden-files: true 79 | -------------------------------------------------------------------------------- /.github/workflows/python_ci_linux.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | name: Linux 4 | 5 | on: 6 | push: 7 | branches-ignore: 8 | - 'repo-helper-update' 9 | - 'pre-commit-ci-update-config' 10 | - 'imgbot' 11 | tags: 12 | - '*' 13 | pull_request: 14 | 15 | permissions: 16 | actions: write 17 | issues: write 18 | contents: read 19 | 20 | jobs: 21 | tests: 22 | name: "ubuntu-22.04 / Python ${{ matrix.config.python-version }}" 23 | runs-on: "ubuntu-22.04" 24 | continue-on-error: ${{ matrix.config.experimental }} 25 | env: 26 | USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12' 27 | 28 | strategy: 29 | fail-fast: False 30 | matrix: 31 | config: 32 | - {python-version: "3.7", testenvs: "py37,build", experimental: False} 33 | - {python-version: "3.8", testenvs: "py38,build", experimental: False} 34 | - {python-version: "3.9", testenvs: "py39,build", experimental: False} 35 | - {python-version: "3.10", testenvs: "py310,build", experimental: False} 36 | - {python-version: "3.11", testenvs: "py311,build", experimental: False} 37 | - {python-version: "3.12", testenvs: "py312,build", experimental: False} 38 | 39 | steps: 40 | - name: Checkout 🛎️ 41 | uses: "actions/checkout@v4" 42 | 43 | - name: Check for changed files 44 | if: startsWith(github.ref, 'refs/tags/') != true 45 | uses: dorny/paths-filter@v2 46 | id: changes 47 | with: 48 | list-files: "json" 49 | filters: | 50 | code: 51 | - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' 52 | 53 | - name: Setup Python 🐍 54 | id: setup-python 55 | if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} 56 | uses: "actions/setup-python@v5" 57 | with: 58 | python-version: "${{ matrix.config.python-version }}" 59 | 60 | - name: Install dependencies 🔧 61 | if: steps.setup-python.outcome == 'success' 62 | run: | 63 | python -VV 64 | python -m site 65 | python -m pip install --upgrade pip setuptools wheel 66 | python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 67 | python -m pip install --upgrade coverage_pyver_pragma 68 | sudo add-apt-repository universe 69 | sudo apt update 70 | sudo apt install libhdf5-dev netcdf-bin libnetcdf-dev 71 | 72 | - name: "Run Tests for Python ${{ matrix.config.python-version }}" 73 | if: steps.setup-python.outcome == 'success' 74 | run: python -m tox -e "${{ matrix.config.testenvs }}" -s false 75 | 76 | - name: "Upload Coverage 🚀" 77 | uses: actions/upload-artifact@v4 78 | if: ${{ always() && steps.setup-python.outcome == 'success' }} 79 | with: 80 | name: "coverage-${{ matrix.config.python-version }}" 81 | path: .coverage 82 | include-hidden-files: true 83 | 84 | 85 | Coverage: 86 | needs: tests 87 | runs-on: "ubuntu-22.04" 88 | steps: 89 | - name: Checkout 🛎️ 90 | uses: "actions/checkout@v4" 91 | 92 | - name: Setup Python 🐍 93 | uses: "actions/setup-python@v5" 94 | with: 95 | python-version: 3.8 96 | 97 | - name: Install dependencies 🔧 98 | run: | 99 | python -m pip install --upgrade pip setuptools wheel 100 | python -m pip install --upgrade "coveralls>=3.0.0" coverage_pyver_pragma 101 | 102 | - name: "Download Coverage 🪂" 103 | uses: actions/download-artifact@v4 104 | with: 105 | path: coverage 106 | 107 | - name: Display structure of downloaded files 108 | id: show 109 | run: ls -R 110 | working-directory: coverage 111 | continue-on-error: true 112 | 113 | - name: Combine Coverage 👷 114 | if: ${{ steps.show.outcome != 'failure' }} 115 | run: | 116 | shopt -s globstar 117 | python -m coverage combine coverage/**/.coverage 118 | 119 | - name: "Upload Combined Coverage Artefact 🚀" 120 | if: ${{ steps.show.outcome != 'failure' }} 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: "combined-coverage" 124 | path: .coverage 125 | include-hidden-files: true 126 | 127 | - name: "Upload Combined Coverage to Coveralls" 128 | if: ${{ steps.show.outcome != 'failure' }} 129 | env: 130 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 131 | run: | 132 | coveralls --service=github 133 | 134 | Deploy: 135 | needs: tests 136 | 137 | runs-on: "ubuntu-22.04" 138 | steps: 139 | - name: Checkout 🛎️ 140 | uses: "actions/checkout@v4" 141 | if: startsWith(github.ref, 'refs/tags/') 142 | 143 | - name: Setup Python 🐍 144 | uses: "actions/setup-python@v5" 145 | if: startsWith(github.ref, 'refs/tags/') 146 | with: 147 | python-version: 3.8 148 | 149 | - name: Install dependencies 🔧 150 | if: startsWith(github.ref, 'refs/tags/') 151 | run: | 152 | python -m pip install --upgrade pip setuptools wheel 153 | python -m pip install --upgrade tox~=3.0 154 | 155 | - name: Build distributions 📦 156 | if: startsWith(github.ref, 'refs/tags/') 157 | run: | 158 | tox -e build 159 | rm -rf dist/*.whl 160 | 161 | - name: Upload distribution to PyPI 🚀 162 | if: startsWith(github.ref, 'refs/tags/') 163 | uses: pypa/gh-action-pypi-publish@v1.4.2 164 | with: 165 | user: __token__ 166 | password: ${{ secrets.PYPI_TOKEN }} 167 | skip_existing: true 168 | 169 | - name: Close milestone 🚪 170 | if: startsWith(github.ref, 'refs/tags/') 171 | run: | 172 | python -m pip install --upgrade github3.py packaging 173 | python .github/milestones.py 174 | env: 175 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | *.egg* 23 | *.manifest 24 | *.spec 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | htmlcov/ 28 | .tox/ 29 | .coverage 30 | .coverage.* 31 | .cache 32 | nosetests.xml 33 | coverage.xml 34 | *.cover 35 | .hypothesis/ 36 | .pytest_cache/ 37 | cover/ 38 | *.mo 39 | *.pot 40 | *.log 41 | local_settings.py 42 | db.sqlite3 43 | instance/ 44 | .webassets-cache 45 | .scrapy 46 | docs/_build/ 47 | doc/build 48 | target/ 49 | .ipynb_checkpoints 50 | .python-version 51 | celerybeat-schedule 52 | celerybeat.pid 53 | *.sage.py 54 | .env 55 | .venv 56 | env/ 57 | venv/ 58 | ENV/ 59 | env.bak/ 60 | venv.bak/ 61 | .spyderproject 62 | .spyproject 63 | .ropeproject 64 | /site 65 | .mypy_cache/ 66 | .dmypy.json 67 | dmypy.json 68 | *.iml 69 | *.ipr 70 | cmake-build-*/ 71 | .idea/**/mongoSettings.xml 72 | *.iws 73 | out/ 74 | atlassian-ide-plugin.xml 75 | com_crashlytics_export_strings.xml 76 | crashlytics.properties 77 | crashlytics-build.properties 78 | fabric.properties 79 | .idea 80 | build 81 | **/__pycache__ 82 | **/conda 83 | __pypackages__/ 84 | profile_default/ 85 | ipython_config.py 86 | Pipfile.lock 87 | .pyre/ 88 | */mainlib 89 | **/.vagrant 90 | **/old 91 | /src/pyms_nist_search/constants.h 92 | _core.cpython* 93 | **/nist_jdx_files 94 | **/MoNA-export-GC-MS_Spectra.json 95 | -------------------------------------------------------------------------------- /.imgbotconfig: -------------------------------------------------------------------------------- 1 | { 2 | "schedule": "weekly", 3 | "ignoredFiles": [ 4 | "**/*.svg" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | --- 3 | 4 | exclude: ^$ 5 | 6 | ci: 7 | autoupdate_schedule: quarterly 8 | 9 | repos: 10 | - repo: https://github.com/repo-helper/pyproject-parser 11 | rev: v0.13.0 12 | hooks: 13 | - id: reformat-pyproject 14 | 15 | - repo: https://github.com/pre-commit/pre-commit-hooks 16 | rev: v3.4.0 17 | hooks: 18 | - id: check-added-large-files 19 | - id: check-ast 20 | - id: fix-byte-order-marker 21 | - id: check-byte-order-marker 22 | - id: check-case-conflict 23 | - id: check-executables-have-shebangs 24 | - id: check-json 25 | - id: check-toml 26 | - id: check-yaml 27 | - id: check-merge-conflict 28 | - id: check-symlinks 29 | - id: check-vcs-permalinks 30 | - id: detect-private-key 31 | - id: trailing-whitespace 32 | - id: mixed-line-ending 33 | - id: end-of-file-fixer 34 | 35 | - repo: https://github.com/domdfcoding/pre-commit-hooks 36 | rev: v0.4.0 37 | hooks: 38 | - id: requirements-txt-sorter 39 | args: 40 | - --allow-git 41 | - id: check-docstring-first 42 | exclude: ^(doc-source/conf|__pkginfo__|setup|tests/.*)\.py$ 43 | - id: bind-requirements 44 | 45 | - repo: https://github.com/python-formate/flake8-dunder-all 46 | rev: v0.4.1 47 | hooks: 48 | - id: ensure-dunder-all 49 | files: ^pyms_nist_search/.*\.py$ 50 | 51 | - repo: https://github.com/domdfcoding/flake2lint 52 | rev: v0.4.3 53 | hooks: 54 | - id: flake2lint 55 | 56 | - repo: https://github.com/pre-commit/pygrep-hooks 57 | rev: v1.10.0 58 | hooks: 59 | - id: python-no-eval 60 | - id: rst-backticks 61 | - id: rst-directive-colons 62 | - id: rst-inline-touching-normal 63 | 64 | - repo: https://github.com/asottile/pyupgrade 65 | rev: v2.12.0 66 | hooks: 67 | - id: pyupgrade 68 | args: 69 | - --py36-plus 70 | - --keep-runtime-typing 71 | 72 | - repo: https://github.com/Lucas-C/pre-commit-hooks 73 | rev: v1.5.1 74 | hooks: 75 | - id: remove-crlf 76 | - id: forbid-crlf 77 | 78 | - repo: https://github.com/python-formate/snippet-fmt 79 | rev: v0.1.5 80 | hooks: 81 | - id: snippet-fmt 82 | 83 | - repo: https://github.com/python-formate/formate 84 | rev: v0.8.0 85 | hooks: 86 | - id: formate 87 | exclude: ^(doc-source/conf|__pkginfo__|setup)\.(_)?py$ 88 | 89 | - repo: https://github.com/python-coincidence/dep_checker 90 | rev: v0.8.0 91 | hooks: 92 | - id: dep_checker 93 | args: 94 | - pyms_nist_search 95 | - --work-dir 96 | - src/ 97 | - --req-file 98 | - ../requirements.txt 99 | 100 | # Custom hooks can be added below this comment 101 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. Don't edit it directly. 2 | # Read the Docs configuration file 3 | --- 4 | version: 2 5 | sphinx: 6 | builder: html 7 | configuration: doc-source/conf.py 8 | formats: 9 | - pdf 10 | - htmlzip 11 | python: 12 | install: 13 | - requirements: requirements.txt 14 | - requirements: doc-source/requirements.txt 15 | build: 16 | os: ubuntu-22.04 17 | tools: 18 | python: '3.9' 19 | jobs: 20 | post_create_environment: 21 | - pip install . 22 | post_install: 23 | - pip install sphinxcontrib-applehelp==1.0.4 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.1 24 | sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Contributing 3 | ============== 4 | 5 | .. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md 6 | 7 | ``pyms-nist-search`` uses `tox `_ to automate testing and packaging, 8 | and `pre-commit `_ to maintain code quality. 9 | 10 | Install ``pre-commit`` with ``pip`` and install the git hook: 11 | 12 | .. code-block:: bash 13 | 14 | $ python -m pip install pre-commit 15 | $ pre-commit install 16 | 17 | 18 | Coding style 19 | -------------- 20 | 21 | `formate `_ is used for code formatting. 22 | 23 | It can be run manually via ``pre-commit``: 24 | 25 | .. code-block:: bash 26 | 27 | $ pre-commit run formate -a 28 | 29 | 30 | Or, to run the complete autoformatting suite: 31 | 32 | .. code-block:: bash 33 | 34 | $ pre-commit run -a 35 | 36 | 37 | Automated tests 38 | ------------------- 39 | 40 | Tests are run with ``tox`` and ``pytest``. 41 | To run tests for a specific Python version, such as Python 3.10: 42 | 43 | .. code-block:: bash 44 | 45 | $ tox -e py310 46 | 47 | 48 | To run tests for all Python versions, simply run: 49 | 50 | .. code-block:: bash 51 | 52 | $ tox 53 | 54 | 55 | Type Annotations 56 | ------------------- 57 | 58 | Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: 59 | 60 | .. code-block:: bash 61 | 62 | $ tox -e mypy 63 | 64 | 65 | 66 | Build documentation locally 67 | ------------------------------ 68 | 69 | The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: 70 | 71 | .. code-block:: bash 72 | 73 | $ tox -e docs 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include requirements.txt 3 | prune **/__pycache__ 4 | include THIRD_PARTY_COPYRIGHT 5 | recursive-include src/pyms_nist_search * 6 | recursive-exclude MoNA_GCMS_Library * 7 | recursive-exclude src/pyms_nist_search *.so 8 | recursive-include src/pyms_nist_search/templates * 9 | recursive-include src/pyms_nist_search *.c 10 | recursive-include src/pyms_nist_search *.C 11 | recursive-include src/pyms_nist_search *.h 12 | recursive-include src/pyms_nist_search *.H 13 | recursive-include src/pyms_nist_search *.dll 14 | recursive-include src/pyms_nist_search *.lib 15 | recursive-include src/pyms_nist_search *.pyi 16 | include src/pyms_nist_search/py.typed 17 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/AUTHORS: -------------------------------------------------------------------------------- 1 | KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH 2 | All contributions licensed under CC BY-NC-SA 3 | 4 | TAJIMA S, GUNMA COLLEGE OF TECHNOLOGY 5 | All contributions licensed under CC BY-NC-SA 6 | 7 | YAMAMOTO M, DEP. CHEMISTRY, FAC. SCIENCE, NARA WOMEN'S UNIV. 8 | All contributions licensed under CC BY-NC-SA 9 | 10 | FUKUSHIMA K, INST. FOR CHEMOBIODYNAMICS, CHIBA UNIV. 11 | All contributions licensed under CC BY-NC-SA 12 | 13 | SASAKI S, TOYOHASHI UNIV. OF TECH. 14 | All contributions licensed under CC BY-NC-SA 15 | 16 | HASHIMOTO K, KYOTO COLLEGE OF PHARMACY 17 | All contributions licensed under CC BY-NC-SA 18 | 19 | TAKATORI M, DEPT. OF CHEMISTRY, FAC. OF EDUC., FUKUSHIMA UNIV. 20 | All contributions licensed under CC BY-NC-SA 21 | 22 | NAKAYAMA M, MASS SPECTROSCOPY SOC. OF JAPAN (MSSJ) 23 | All contributions licensed under CC BY-NC-SA 24 | 25 | ITO S, DEPT. OF CHEM., TOHOKU UNIV. 26 | All contributions licensed under CC BY-NC-SA 27 | 28 | MASS SPECTROSCOPY SOC. OF JAPAN (MSSJ) 29 | All contributions licensed under CC BY-NC-SA 30 | 31 | HAYASHI A, DEPT. OF CHEMISTRY, FAC. OF SCI. AND TECHNOLOGY, KINKI UNIV. 32 | All contributions licensed under CC BY-NC-SA 33 | 34 | KAMEOKA H, DEPT. OF APPLIED CHEMISTRY, FAC. OF SCI. AND ENGINEERING, KINKI UNIV. 35 | All contributions licensed under CC BY-NC-SA 36 | 37 | HORI T, DEPT. OF CHEMISTRY, FAC. OF LIBERAL ARTS AND EDUCATION, SHIGA UNIV. 38 | All contributions licensed under CC BY-NC-SA 39 | 40 | NAGAKI M, DEPT. OF GENERAL EDUC., HIROSAKI UNIV. 41 | All contributions licensed under CC BY-NC-SA 42 | 43 | SEYAMA Y, DEPT. OF PHYSIOL. CHEMISTRY AND NUTRITION, FAC. OF MEDICINE, UNIV. OF TOKYO 44 | All contributions licensed under CC BY-NC-SA 45 | 46 | ENDO K, PHARMACEUTICAL INST., TOHOKU UNIV. 47 | All contributions licensed under CC BY-NC-SA 48 | 49 | YOSHIZUMI H, FAC. OF PHARMACY, MEIJO UNIV. 50 | All contributions licensed under CC BY-NC-SA 51 | 52 | NAKATA H, DEPT. OF CHEMISTRY, AICHI KYOIKU UNIV. 53 | All contributions licensed under CC BY-NC-SA 54 | 55 | ISA K, DEPT. OF CHEM., FAC. OF EDUC., FUKUI UNIV. 56 | All contributions licensed under CC BY-NC-SA 57 | 58 | HAGIWARA H, CHEM. RES. INST. OF NON-AQUEOUS SOLUTIONS, TOHOKU UNIV. 59 | All contributions licensed under CC BY-NC-SA 60 | 61 | KATO T, DEPT. OF CHEM., FAC. OF SCI., TOHOKU UNIV. 62 | All contributions licensed under CC BY-NC-SA 63 | 64 | HAYASHI A, FAC. OF SCIENCE AND TECHNOLOGY, KINKI UNIV. 65 | All contributions licensed under CC BY-NC-SA 66 | 67 | HISAHIRO HAGIWARA, CHEMICAL RESEARCH INSTITUTE OF NON-AQUEOUSSOLUTIONS TOHOKU UNIV. 68 | All contributions licensed under CC BY-NC-SA 69 | 70 | YAMAOKA R, KYOTO INSTITUTE OF TECHNOLOGY 71 | All contributions licensed under CC BY-NC-SA 72 | 73 | HIRAMA M, TOHOKU UNIV. FACULTY OF SCIENCE DEPT. OF CHEMISTRY YUKIBUNSEKI 74 | All contributions licensed under CC BY-NC-SA 75 | 76 | HAMAMATSU UNIV. SCHOOL OF MEDICINE 77 | All contributions licensed under CC BY-NC-SA 78 | 79 | FUJISE Y, HAMAMATSU UNIV. SCHOOL OF MEDICINE 80 | All contributions licensed under CC BY-NC-SA 81 | 82 | EGUCHI S, FAC. OF SCIENCE, HIROSHIMA UNIV. 83 | All contributions licensed under CC BY-NC-SA 84 | 85 | SODA AROMATIC CO., LTD. 86 | All contributions licensed under CC BY-NC-SA 87 | 88 | TAKEUCHI T, DEP. CHEMISTRY, FAC. SCIENCE, NARA WOMEN'S UNIV. 89 | All contributions licensed under CC BY-NC-SA 90 | 91 | KYOTO INSTITUTE OF TECHNOLOGY 92 | All contributions licensed under CC BY-NC-SA 93 | 94 | MAKI Y, GOVERMENT INDUSTRIAL RESEARCH INSTITUTE, NAGOYA 95 | All contributions licensed under CC BY-NC-SA 96 | 97 | NARA WOMEN'S UNIVERSITY 98 | All contributions licensed under CC BY-NC-SA 99 | 100 | UBE SCIENTIFIC ANALYSIS LABORATORY 101 | All contributions licensed under CC BY-NC-SA 102 | 103 | TSUNODA T, FACULTY OF PHARMACEUTICAL SCIENCES, TOKUSHIMA BUNRI UNIV. 104 | All contributions licensed under CC BY-NC-SA 105 | 106 | UOEH 107 | All contributions licensed under CC BY-NC-SA 108 | 109 | KANEMASA S, INST. OF ADVANCED MATERIAL STUDY KYUSHU UNIV. 110 | All contributions licensed under CC BY-NC-SA 111 | 112 | TASHIRO M, INST. OF ADVANCED MATERIAL STUDY KYUSHU UNIV. 113 | All contributions licensed under CC BY-NC-SA 114 | 115 | GUNMA COLLEGE OF TECHNOLOGY 116 | All contributions licensed under CC BY-NC-SA 117 | 118 | THE INSTITUTE OF SCIENTIFIC AND INDUSTRIALRESEARCH 119 | All contributions licensed under CC BY-NC-SA 120 | 121 | KURARAY CO., LTD. 122 | All contributions licensed under CC BY-NC-SA 123 | 124 | SUMITA Y, KYOTO COLLEGE OF PHARMACY 125 | All contributions licensed under CC BY-NC-SA 126 | 127 | HISAHIRO HAGIWARA, INSTITUTE FOR CHEMICAL REACTION SCIENCE TOHOKU UNIV. 128 | All contributions licensed under CC BY-NC-SA 129 | 130 | TAKESHITA H, INST. OF ADVANCED MATERIAL STUDY KYUSHU UNIV. 131 | All contributions licensed under CC BY-NC-SA 132 | 133 | SAWADA M, THE INST. OF SCIENTIFIC AND INDUSTRIALRESEARCH OSAKA UNIV. 134 | All contributions licensed under CC BY-NC-SA 135 | 136 | INST. OF ADVANCED MATERIAL STUDY KYUSHU UNIV. 137 | All contributions licensed under CC BY-NC-SA 138 | 139 | FAC. TECHNOLOGY, HIROSHIMA UNIV. 140 | All contributions licensed under CC BY-NC-SA 141 | 142 | YOSHINORI Y, FAC. SCIENCE, TOHOKU UNIV. 143 | All contributions licensed under CC BY-NC-SA 144 | 145 | YOSHIKI O, FAC, TECHNOLOGY, OSAKA UNIV. 146 | All contributions licensed under CC BY-NC-SA 147 | 148 | ISHIKAWA M, HIROSHIMA UNIV. FAC. TEQUNOLOGY 149 | All contributions licensed under CC BY-NC-SA 150 | 151 | IIDA Y, DAISHIMA S, FAC. OF ENGINEERING, SEIKEI UNIV. 152 | All contributions licensed under CC BY-NC-SA 153 | 154 | Miyagawa H, Akimoto S, Yamasaki K, GL Sciences Inc. 155 | All contributions licensed under CC BY-SA 156 | 157 | Ara T, Morishita Y, Shibata D, Kazusa DNA Research Institute 158 | All contributions licensed under CC BY-SA 159 | 160 | Takashi Iida, Department of Chemistry, College of Humanities and Sciences, Nihon University 161 | All contributions licensed under CC BY-NC-SA 162 | 163 | Takashi Iida, Department of Chemistry, College of Humanities and Sciences, Nihon University. Koichi Metori, Chemical Analytical Center, College of Pharmacy, Nihon Univercity. 164 | All contributions licensed under CC BY-NC-SA 165 | 166 | Tsujimoto Y, Tsugawa H, Bamba T, Fukusaki E, engineering department, Osaka Univ. 167 | All contributions licensed under CC BY-SA 168 | 169 | Dempo Y, Bamba T, Fukusaki E, engineering department, Osaka Univ. 170 | All contributions licensed under CC BY-SA 171 | 172 | Kusano M, Fukushima A, Plant Science Center, RIKEN. 173 | All contributions licensed under CC BY-SA 174 | 175 | Kimura Y, Inokuchi T, Faculty of Agriculture, Tottori University 176 | All contributions licensed under CC BY-SA 177 | 178 | Kimura Y, Kuramata M, Faculty of Agriculture, Tottori University 179 | All contributions licensed under CC BY-SA 180 | 181 | Kimura Y, Kusano M, Faculty of Agriculture, Tottori University 182 | All contributions licensed under CC BY-SA 183 | 184 | Kimura Y, Sawada A, Faculty of Agriculture, Tottori University 185 | All contributions licensed under CC BY-SA 186 | 187 | Hiroyuki Morii, Department of Chemistry, University of Occupational and Environmental Health 188 | All contributions licensed under CC BY-SA 189 | 190 | Hiroyuki Morii, Department of Environmental Management, and Chemistry, University of Occupational and Environmental Health 191 | All contributions licensed under CC BY-SA 192 | 193 | Shimadzu Corporation., Kyoto, Japan 194 | All contributions licensed under CC BY 195 | 196 | University of California, Davis 197 | All contributions licensed under CC BY 4.0 198 | 199 | University of Alberta 200 | All contributions licensed under CC BY 4.0 201 | 202 | Osaka University 203 | All contributions licensed under CC BY 4.0 204 | 205 | Wake Forest School of Medicine 206 | All contributions licensed under CC BY 4.0 207 | 208 | Biswapriya B. Misra, Michael Olivier 209 | All contributions licensed under CC BY 4.0 210 | 211 | Masayo Sekimoto, Takemichi Nakamura, Molecular Structure Characterization Unit, RIKEN 212 | All contributions licensed under CC BY 213 | 214 | Dimitrios E. Damalas, Stefanos Kokolakis, Reza Aalizadeh, Nikolaos Thomaidis, University of Athens 215 | All contributions licensed under CC BY 216 | 217 | Kuhara T 218 | All contributions licensed under CC BY 4.0 219 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA-export-GC-MS_Spectra-json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA-export-GC-MS_Spectra-json.zip -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/ALNUMNAM.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/ALNUMNAM.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/ALPHANAM.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/ALPHANAM.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/FORM.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/FORM.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/LOSS.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/LOSS.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/LOSS.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/LOSS.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/MAXMASS.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/MAXMASS.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/MAXMASS.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/MAXMASS.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/MW.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/MW.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/NAME.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/NAME.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/NAMESORT.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/NAMESORT.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK_AM0.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK_AM0.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK_AM0.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK_AM0.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK_AM2.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK_AM2.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/PEAK_AM2.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/PEAK_AM2.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/REGISTRY.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/REGISTRY.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/USER.DBU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/USER.DBU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/USER.INU: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/MoNA_GCMS_Library/MoNA/USER.INU -------------------------------------------------------------------------------- /MoNA_GCMS_Library/MoNA/USERID.NUM: -------------------------------------------------------------------------------- 1 | ID Numbers, Times and Names in the User Spectra Database 2 | 3 | ID # DATE and TIME NAME 4 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | """ 5 | Utility for parsing datafiles from `MassBank of North America`_. 6 | 7 | .. _MassBank of North America: https://mona.fiehnlab.ucdavis.edu/ 8 | """ 9 | # 10 | # This file is part of PyMassSpec NIST Search 11 | # Python interface to the NIST MS Search DLL 12 | # 13 | # Copyright (c) 2020 Dominic Davis-Foster 14 | # 15 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 16 | # it under the terms of the GNU Lesser General Public License as 17 | # published by the Free Software Foundation; either version 3 of 18 | # the License, or (at your option) any later version. 19 | # 20 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU Lesser General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU Lesser General Public 26 | # License along with this program; if not, write to the Free Software 27 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 28 | # MA 02110-1301, USA. 29 | # 30 | 31 | # this package 32 | from . import parse_mona_contributors, parse_mona_json # noqa: F401 33 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/parse_json_and_contribs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # parse_mona_contributors.py 4 | """ 5 | Script to both parse MoNA JSON data into a single MSP file and compile a contributors to 6 | the MoNA library, in a more efficient manner than doing them separately. 7 | """ # noqa: D400 8 | # 9 | # This file is part of PyMassSpec NIST Search 10 | # Python interface to the NIST MS Search DLL 11 | # 12 | # Copyright (c) 2020-2022 Dominic Davis-Foster 13 | # 14 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 15 | # it under the terms of the GNU Lesser General Public License as 16 | # published by the Free Software Foundation; either version 3 of 17 | # the License, or (at your option) any later version. 18 | # 19 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU Lesser General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU Lesser General Public 25 | # License along with this program; if not, write to the Free Software 26 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 27 | # MA 02110-1301, USA. 28 | # 29 | 30 | # stdlib 31 | from typing import List 32 | 33 | # 3rd party 34 | from domdf_python_tools.paths import PathPlus 35 | 36 | # this package 37 | import MoNA_GCMS_Library 38 | from pyms_nist_search import ReferenceData 39 | from pyms_nist_search.mona_tools import mass_spec_from_mona, parse_metadata 40 | 41 | __all__: List[str] = [] 42 | 43 | 44 | def main() -> None: 45 | """ 46 | Command line entry point. 47 | """ 48 | 49 | contributors = MoNA_GCMS_Library.parse_mona_contributors.ContributorList() 50 | 51 | # Create ReferenceData and write to file 52 | with (PathPlus(MoNA_GCMS_Library.__file__).parent / "MoNA.msp").open('w') as msp_fp: 53 | 54 | for comp in MoNA_GCMS_Library.parse_mona_json.load_mona_json(): 55 | 56 | compound: dict = comp["compound"][0] 57 | names: list = compound["names"] 58 | name: str = names[0]["name"] 59 | synonyms: list = [name for name in names[1:]] 60 | 61 | mass_spec = mass_spec_from_mona(comp["spectrum"]) 62 | 63 | properties_dict = parse_metadata(comp) 64 | 65 | # Contributors 66 | contributor = contributors.add_contributor(properties_dict["contributor"]) 67 | contributor.add_contribution(**properties_dict) 68 | 69 | # MSP 70 | del properties_dict["license"] 71 | 72 | ref_data = ReferenceData( 73 | name=name, 74 | mass_spec=mass_spec, 75 | synonyms=synonyms, 76 | **properties_dict, 77 | ) 78 | 79 | msp = ref_data.to_msp() 80 | print(msp) 81 | msp_fp.write(msp) 82 | 83 | contributors.write_authors_file() 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/parse_mona_contributors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # parse_mona_contributors.py 4 | """ 5 | Script to compile a contributors to the MoNA library, 6 | and the license the contributions are licensed under. 7 | """ # noqa: D400 8 | # 9 | # This file is part of PyMassSpec NIST Search 10 | # Python interface to the NIST MS Search DLL 11 | # 12 | # Copyright (c) 2020-2022 Dominic Davis-Foster 13 | # 14 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 15 | # it under the terms of the GNU Lesser General Public License as 16 | # published by the Free Software Foundation; either version 3 of 17 | # the License, or (at your option) any later version. 18 | # 19 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU Lesser General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU Lesser General Public 25 | # License along with this program; if not, write to the Free Software 26 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 27 | # MA 02110-1301, USA. 28 | # 29 | 30 | # stdlib 31 | from typing import Dict, List, Optional, Sequence, Union 32 | 33 | # 3rd party 34 | from domdf_python_tools.compat import importlib_resources 35 | 36 | # this package 37 | import MoNA_GCMS_Library 38 | from MoNA_GCMS_Library.parse_mona_json import load_mona_json 39 | from pyms_nist_search.mona_tools import parse_metadata 40 | 41 | __all__ = ("Contributor", "Record", "ContributorList") 42 | 43 | 44 | class Contributor: 45 | """ 46 | Class to model contributor to MoNA library. 47 | 48 | :param name: The name of the contributor 49 | :param contributions: A list of records contributed 50 | """ 51 | 52 | def __init__(self, name: str, contributions: Optional[List["Record"]] = None): 53 | self.name = name 54 | if contributions: 55 | self.contributions = contributions 56 | else: 57 | self.contributions = [] 58 | 59 | def __eq__(self, other) -> bool: # noqa: MAN001 60 | if isinstance(other, str): 61 | return self.name == other 62 | elif isinstance(other, self.__class__): 63 | return self.name == other.name 64 | else: 65 | return NotImplemented 66 | 67 | @classmethod 68 | def from_mona_dict(cls, mona_data: Dict): 69 | """ 70 | Construct an object from Massbank of North America json data that has been loaded into a dictionary. 71 | 72 | :param mona_data: 73 | """ 74 | 75 | def add_contribution(self, id_: str, license_: Optional[str] = None, **kwargs) -> "Record": 76 | r""" 77 | Add a contribution made by this Contributor, and return the new :class:`~.Record` object created. 78 | 79 | :param id\_: The ID of the contribution 80 | :param license\_: The license the contribution is licensed under 81 | 82 | :return: A :class:`~.Record` object representing the contribution 83 | """ 84 | 85 | if not license_: 86 | record = Record(id_) 87 | else: 88 | record = Record(id_, license_, **kwargs) 89 | 90 | self.add_record(record) 91 | return record 92 | 93 | def add_record(self, record: "Record") -> None: 94 | """ 95 | Add a :class:`~.Record` object representing a contribution made by this Contributor. 96 | 97 | :param record: A :class:`Record` object representing the contribution 98 | """ 99 | 100 | self.contributions.append(record) 101 | 102 | 103 | class Record: 104 | r""" 105 | Class to model a Mass Spectrum record in the MoNA library. 106 | 107 | :param id\_: The ID of the record 108 | :param license\_: The license of the record. 109 | 110 | Any additional arguments are ignored. 111 | """ 112 | 113 | def __init__(self, id_: str, license_: str = "CC BY 4.0", **kwargs): 114 | self.id = id_ 115 | self.license = license_ 116 | 117 | @classmethod 118 | def from_mona_dict(cls, mona_data: Dict) -> "Record": 119 | """ 120 | Construct an object from Massbank of North America JSON data that has been loaded into a dictionary. 121 | 122 | :param mona_data: 123 | """ 124 | 125 | properties_dict = parse_metadata(mona_data) 126 | 127 | if not properties_dict["license"]: 128 | return cls(properties_dict["id"]) 129 | else: 130 | return cls(**properties_dict) 131 | 132 | 133 | class ContributorList(list, Sequence[Union[str, Contributor]]): 134 | """ 135 | A list of :class:`~.Contributor` objects. 136 | """ 137 | 138 | def add_contributor(self, contributor_name: str) -> Contributor: 139 | """ 140 | Add a new contributor to the list and return the :class:`~.Contributor` object representing them. 141 | 142 | :param contributor_name: The name of the contributor. 143 | """ 144 | 145 | if contributor_name in self: 146 | return self[self.index(contributor_name)] 147 | else: 148 | new_contributor = Contributor(contributor_name) 149 | self.append(new_contributor) 150 | return new_contributor 151 | 152 | def get_contributor(self, contributor_name: str) -> Optional[Contributor]: 153 | """ 154 | Returns the :class:`~Contributor` object representing the contributor with the given name, 155 | or :py:obj:`None` if no such contributor exists. 156 | 157 | :param contributor_name: The name of the contributor 158 | """ # noqa: D400 159 | 160 | if contributor_name in self: 161 | return self[self.index(contributor_name)] 162 | else: 163 | return None 164 | 165 | def write_authors_file(self) -> None: 166 | """ 167 | Generate the ``AUTHORS`` file, listing the contributors to the MoNA database. 168 | """ 169 | 170 | with importlib_resources.path(MoNA_GCMS_Library, "AUTHORS") as authors_file: 171 | with authors_file.open('w') as fp: 172 | 173 | for contributor in self: 174 | print(contributor.name) 175 | fp.write(f"{contributor.name}\n") 176 | 177 | for license_ in {"CC BY", "CC BY 4.0", "CC BY-SA", "CC BY-NC-SA"}: 178 | if all([record.license == license_ for record in contributor.contributions]): 179 | print(f"\tAll contributions licensed under {license_}") 180 | fp.write(f"\tAll contributions licensed under {license_}\n") 181 | break 182 | else: 183 | for record in contributor.contributions: 184 | print(f"\tid: {record.id} \t License: {record.license}") 185 | fp.write(f"\tid: {record.id} \t License: {record.license}\n") 186 | 187 | print() 188 | fp.write('\n') 189 | 190 | @classmethod 191 | def from_mona_dict(cls, mona_data: List[Dict]) -> "ContributorList": 192 | """ 193 | Construct a :class:`~.ContributorList` from the MoNA database. 194 | 195 | :param mona_data: The database, parsed from the JSON file. 196 | """ 197 | 198 | contributors = cls() 199 | 200 | for comp in mona_data: 201 | record = Record.from_mona_dict(comp) 202 | name = parse_metadata(comp)["contributor"] 203 | 204 | contributor = contributors.add_contributor(name) 205 | contributor.contributions.append(record) 206 | 207 | return contributors 208 | 209 | 210 | def main() -> None: 211 | mona_data = load_mona_json() 212 | contributor_list = ContributorList.from_mona_dict(mona_data) 213 | contributor_list.write_authors_file() 214 | 215 | 216 | if __name__ == "__main__": 217 | main() 218 | -------------------------------------------------------------------------------- /MoNA_GCMS_Library/parse_mona_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # parse_mona_json.py 4 | """ 5 | Script to parse MoNA JSON data into ReferenceData objects and then convert them into a single MSP file. 6 | That file can then be converted into a NIST User Library using the Lib2Nist program from 7 | https://chemdata.nist.gov/mass-spc/ms-search/Library_conversion_tool.html 8 | """ 9 | # 10 | # This file is part of PyMassSpec NIST Search 11 | # Python interface to the NIST MS Search DLL 12 | # 13 | # Copyright (c) 2020 Dominic Davis-Foster 14 | # 15 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 16 | # it under the terms of the GNU Lesser General Public License as 17 | # published by the Free Software Foundation; either version 3 of 18 | # the License, or (at your option) any later version. 19 | # 20 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU Lesser General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU Lesser General Public 26 | # License along with this program; if not, write to the Free Software 27 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 28 | # MA 02110-1301, USA. 29 | # 30 | 31 | # stdlib 32 | import json 33 | import shutil 34 | from typing import Dict, List 35 | 36 | # 3rd party 37 | from domdf_python_tools.compat import importlib_resources 38 | from domdf_python_tools.paths import PathPlus 39 | 40 | # this package 41 | import MoNA_GCMS_Library 42 | from pyms_nist_search import ReferenceData 43 | 44 | __all__ = ("load_mona_json", "create_mona_msp") 45 | 46 | 47 | def load_mona_json() -> List[Dict]: 48 | """ 49 | Loads the MoNA database from a compressed JSON file. 50 | 51 | :return: 52 | """ 53 | 54 | mona_library_dir = PathPlus(__file__).parent 55 | library_json_file = mona_library_dir / "MoNA-export-GC-MS_Spectra.json" 56 | library_zip_file = mona_library_dir / "MoNA-export-GC-MS_Spectra-json.zip" 57 | 58 | # if not library_json_file.is_file() and not library_zip_file.is_file(): 59 | # urllib.request.urlretrieve( 60 | # 'https://mona.fiehnlab.ucdavis.edu/rest/downloads/retrieve/33c87724-3595-4d7e-9bc0-35d1011c7482/', 61 | # str(library_zip_file)) 62 | 63 | if not library_json_file.is_file(): 64 | shutil.unpack_archive(str(library_zip_file), str(mona_library_dir), format="zip") 65 | 66 | return json.loads(importlib_resources.read_text(MoNA_GCMS_Library, "MoNA-export-GC-MS_Spectra.json")) 67 | 68 | 69 | def create_mona_msp() -> None: 70 | """ 71 | Generate ``.msp`` files for each file in the MoNA database. 72 | """ 73 | 74 | # Create ReferenceData and write to file 75 | with (PathPlus(MoNA_GCMS_Library.__file__).parent / "MoNA.msp").open('w') as fp: 76 | for comp in load_mona_json(): 77 | ref_data = ReferenceData.from_mona_dict(comp) 78 | msp = ref_data.to_msp() 79 | # print(msp) 80 | fp.write(msp) 81 | 82 | 83 | def main() -> None: 84 | create_mona_msp() 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /NISTDLL Reference.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/NISTDLL Reference.pdf -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | PyMassSpec NIST Search 3 | ======================== 4 | 5 | .. start short_desc 6 | 7 | **PyMassSpec extension for searching mass spectra using NIST's Mass Spectrum Search Engine.** 8 | 9 | .. end short_desc 10 | 11 | .. image:: https://img.shields.io/appveyor/build/domdfcoding/pyms-nist-search/master?logo=appveyor 12 | :target: https://ci.appveyor.com/project/domdfcoding/pyms-nist-search/branch/master 13 | :alt: AppVeyor Windows Build Status 14 | 15 | .. start shields 16 | 17 | .. list-table:: 18 | :stub-columns: 1 19 | :widths: 10 90 20 | 21 | * - Docs 22 | - |docs| |docs_check| 23 | * - Tests 24 | - |actions_linux| |actions_windows| |coveralls| 25 | * - PyPI 26 | - |pypi-version| |supported-versions| |supported-implementations| |wheel| 27 | * - Activity 28 | - |commits-latest| |commits-since| |maintained| |pypi-downloads| 29 | * - QA 30 | - |codefactor| |actions_flake8| |actions_mypy| 31 | * - Other 32 | - |license| |language| |requires| 33 | 34 | .. |docs| image:: https://img.shields.io/readthedocs/pynist/latest?logo=read-the-docs 35 | :target: https://pynist.readthedocs.io/en/latest 36 | :alt: Documentation Build Status 37 | 38 | .. |docs_check| image:: https://github.com/domdfcoding/pynist/workflows/Docs%20Check/badge.svg 39 | :target: https://github.com/domdfcoding/pynist/actions?query=workflow%3A%22Docs+Check%22 40 | :alt: Docs Check Status 41 | 42 | .. |actions_linux| image:: https://github.com/domdfcoding/pynist/workflows/Linux/badge.svg 43 | :target: https://github.com/domdfcoding/pynist/actions?query=workflow%3A%22Linux%22 44 | :alt: Linux Test Status 45 | 46 | .. |actions_windows| image:: https://github.com/domdfcoding/pynist/workflows/Windows/badge.svg 47 | :target: https://github.com/domdfcoding/pynist/actions?query=workflow%3A%22Windows%22 48 | :alt: Windows Test Status 49 | 50 | .. |actions_flake8| image:: https://github.com/domdfcoding/pynist/workflows/Flake8/badge.svg 51 | :target: https://github.com/domdfcoding/pynist/actions?query=workflow%3A%22Flake8%22 52 | :alt: Flake8 Status 53 | 54 | .. |actions_mypy| image:: https://github.com/domdfcoding/pynist/workflows/mypy/badge.svg 55 | :target: https://github.com/domdfcoding/pynist/actions?query=workflow%3A%22mypy%22 56 | :alt: mypy status 57 | 58 | .. |requires| image:: https://dependency-dash.repo-helper.uk/github/domdfcoding/pynist/badge.svg 59 | :target: https://dependency-dash.repo-helper.uk/github/domdfcoding/pynist/ 60 | :alt: Requirements Status 61 | 62 | .. |coveralls| image:: https://img.shields.io/coveralls/github/domdfcoding/pynist/master?logo=coveralls 63 | :target: https://coveralls.io/github/domdfcoding/pynist?branch=master 64 | :alt: Coverage 65 | 66 | .. |codefactor| image:: https://img.shields.io/codefactor/grade/github/domdfcoding/pynist?logo=codefactor 67 | :target: https://www.codefactor.io/repository/github/domdfcoding/pynist 68 | :alt: CodeFactor Grade 69 | 70 | .. |pypi-version| image:: https://img.shields.io/pypi/v/pyms-nist-search 71 | :target: https://pypi.org/project/pyms-nist-search/ 72 | :alt: PyPI - Package Version 73 | 74 | .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pyms-nist-search?logo=python&logoColor=white 75 | :target: https://pypi.org/project/pyms-nist-search/ 76 | :alt: PyPI - Supported Python Versions 77 | 78 | .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pyms-nist-search 79 | :target: https://pypi.org/project/pyms-nist-search/ 80 | :alt: PyPI - Supported Implementations 81 | 82 | .. |wheel| image:: https://img.shields.io/pypi/wheel/pyms-nist-search 83 | :target: https://pypi.org/project/pyms-nist-search/ 84 | :alt: PyPI - Wheel 85 | 86 | .. |license| image:: https://img.shields.io/github/license/domdfcoding/pynist 87 | :target: https://github.com/domdfcoding/pynist/blob/master/LICENSE 88 | :alt: License 89 | 90 | .. |language| image:: https://img.shields.io/github/languages/top/domdfcoding/pynist 91 | :alt: GitHub top language 92 | 93 | .. |commits-since| image:: https://img.shields.io/github/commits-since/domdfcoding/pynist/v0.7.2 94 | :target: https://github.com/domdfcoding/pynist/pulse 95 | :alt: GitHub commits since tagged version 96 | 97 | .. |commits-latest| image:: https://img.shields.io/github/last-commit/domdfcoding/pynist 98 | :target: https://github.com/domdfcoding/pynist/commit/master 99 | :alt: GitHub last commit 100 | 101 | .. |maintained| image:: https://img.shields.io/maintenance/yes/2025 102 | :alt: Maintenance 103 | 104 | .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/pyms-nist-search 105 | :target: https://pypi.org/project/pyms-nist-search/ 106 | :alt: PyPI - Downloads 107 | 108 | .. end shields 109 | 110 | 111 | PyMassSpec extension for searching mass spectra using NIST's Spectrum Search Engine 112 | 113 | PyMassSpec NIST Search is Free Software licensed under the `GNU Lesser General Public License Version 3 `_ 114 | 115 | A copy of the MassBank of North America database, in JSON, MSP and NIST Library formats, is included for the purposes of these tests. 116 | This library was created on 22 April 2020 using the "parse_mona_json.py" script and Lib2Nist. 117 | Licensed under the CC BY 4.0 License. 118 | For a list of contributors, see the file ``MoNA_GCMS_Library/AUTHORS`` 119 | 120 | .. TODO: add links. 121 | 122 | Installation 123 | -------------- 124 | 125 | .. begin installation 126 | .. end installation 127 | 128 | 129 | Usage 130 | -------- 131 | 132 | You will need to supply your own copy of the NIST Mass Spectral library to use this software. 133 | 134 | The main class in this library is the ``Engine`` class. This class performs the actual searching. Start by initialising the search engine as follows: 135 | 136 | .. code-block:: python 137 | 138 | search = pyms_nist_search.Engine( 139 | FULL_PATH_TO_MAIN_LIBRARY, 140 | pyms_nist_search.NISTMS_MAIN_LIB, 141 | FULL_PATH_TO_WORK_DIR, 142 | ) 143 | 144 | Where ``FULL_PATH_TO_MAIN_LIBRARY`` is the path to the location of your mass spectral library, and ``FULL_PATH_TO_WORK_DIR`` is the path to the working directory to be used by the search engine. 145 | 146 | A ``MassSpectrum`` object can then be searched as follows: 147 | 148 | .. code-block:: python 149 | 150 | search.full_search_with_ref_data(mass_spec) 151 | 152 | This will return a list of tuples consisting of ``SearchResult`` and ``ReferenceData`` objects for the possible identities of the mass spectrum. 153 | 154 | A list of just the ``SearchResult`` objects can be obtained with this method: 155 | 156 | .. code-block:: python 157 | 158 | hit_list = search.full_search(mass_spec) 159 | 160 | For each of these hits, the reference data can be obtained as follows: 161 | 162 | .. code-block:: python 163 | 164 | for hit in hit_list: 165 | ref_data = search.get_reference_data(hit.spec_loc) 166 | 167 | 168 | TODO 169 | ----- 170 | 171 | 1. Write comprehensive tests using pytest 172 | -------------------------------------------------------------------------------- /THIRD_PARTY_COPYRIGHT: -------------------------------------------------------------------------------- 1 | PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 2 | the x86 and x64 directories. Available from 3 | ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 4 | 5 | ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 6 | "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 7 | and are registered in the United States and other countries. 8 | All Rights Reserved. 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | clone_depth: 50 # The same depth Travis has 3 | image: Visual Studio 2019 4 | 5 | environment: 6 | matrix: 7 | # # Python36 8 | # - TOXENV: "py36" 9 | # PYTHON: "C:\\Python36" 10 | # TOX_APPVEYOR_X64: 0 11 | # Python37 12 | - TOXENV: "py37" 13 | PYTHON: "C:\\Python37" 14 | TOX_APPVEYOR_X64: 0 15 | # Python38 16 | - TOXENV: "py38" 17 | PYTHON: "C:\\Python38" 18 | TOX_APPVEYOR_X64: 0 19 | # Python39 20 | - TOXENV: "py39" 21 | PYTHON: "C:\\Python39" 22 | TOX_APPVEYOR_X64: 0 23 | # # Python310 24 | # - TOXENV: "py310" 25 | # PYTHON: "C:\\Python310" 26 | # TOX_APPVEYOR_X64: 0 27 | # # Python36-x64 28 | # - TOXENV: "py36" 29 | # PYTHON: "C:\\Python36-x64" 30 | # TOX_APPVEYOR_X64: 1 31 | # Python37-x64 32 | - TOXENV: "py37" 33 | PYTHON: "C:\\Python37-x64" 34 | TOX_APPVEYOR_X64: 1 35 | # Python38-x64 36 | - TOXENV: "py38" 37 | PYTHON: "C:\\Python38-x64" 38 | TOX_APPVEYOR_X64: 1 39 | # Python39-x64 40 | - TOXENV: "py39" 41 | PYTHON: "C:\\Python39-x64" 42 | TOX_APPVEYOR_X64: 1 43 | # Python310-x64 44 | - TOXENV: "py310" 45 | PYTHON: "C:\\Python310-x64" 46 | TOX_APPVEYOR_X64: 1 47 | # Python311-x64 48 | - TOXENV: "py311" 49 | PYTHON: "C:\\Python311-x64" 50 | TOX_APPVEYOR_X64: 1 51 | # Python312-x64 52 | - TOXENV: "py312" 53 | PYTHON: "C:\\Python312-x64" 54 | TOX_APPVEYOR_X64: 1 55 | 56 | pypi_token: 57 | secure: T7c6dwug8be6NlOGpL44ftGSw9RgNw/HsHNb7xdbhzX1iZ+DbKznSIzxHdIVxSgxx0BqEkLR56CtXOU0AgOmpylc9/yysdd9R8ZOjwobJuCK7Tb+Q+/T+Cvby2fZmuvqAIxwBBS/j2337iFdbTIOR9JN6emUuv3Cmc2Ufzl6bzi7kuUjXo9OmRp87gHq/XCDh1zHGnp0VVMmPOzMORa/+byOY21qh0PW0m3giBdhY4xFm32wGB7GRX1fIx98ttGrC5lMU3k1PXs+RK+bJ/OcamAhRnRG9h6VfIkbNwniV5Y= 58 | 59 | PIP_PREFER_BINARY: 1 60 | 61 | install: 62 | # We need wheel installed to build wheels 63 | - "%PYTHON%\\python.exe -m pip install wheel Cython tox~=3.0 tox-appveyor build" 64 | 65 | build: off 66 | 67 | test_script: 68 | - "%PYTHON%\\python.exe -m tox" 69 | 70 | after_test: 71 | - ".ci\\build.cmd %PYTHON%\\python.exe -m build" 72 | 73 | artifacts: 74 | # bdist_wheel puts your built wheel in the dist directory 75 | - path: dist\*.whl 76 | name: wheels 77 | 78 | #on_success: 79 | # You can use this step to upload your artifacts to a public website. 80 | # See Appveyor's documentation for more details. Or you can simply 81 | # access your wheels from the Appveyor "artifacts" tab for your build. 82 | 83 | deploy_script: 84 | - ps: | 85 | if($env:APPVEYOR_REPO_TAG -eq 'true') { 86 | Write-Output ("Deploying to PyPI...") 87 | &"${Env:PYTHON}/python.exe" -m pip install twine 88 | &"${Env:PYTHON}/python.exe" -m twine upload -u __token__ -p ${Env:pypi_token} --skip-existing dist/*.whl 89 | } else { 90 | Write-Output "No tag for deployment" 91 | } 92 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Perform cleanup after non-graceful shutdown 4 | docker stop pyms-nist-server 5 | docker container rm pyms-nist-server 6 | docker system prune 7 | -------------------------------------------------------------------------------- /copr_header.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | # 4 | # filename.py 5 | # 6 | # This file is part of PyMassSpec NIST Search 7 | # Python interface to the NIST MS Search DLL 8 | # 9 | # Copyright (c) 2020 Dominic Davis-Foster 10 | # 11 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 12 | # it under the terms of the GNU Lesser General Public License as 13 | # published by the Free Software Foundation; either version 3 of 14 | # the License, or (at your option) any later version. 15 | # 16 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU Lesser General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU Lesser General Public 22 | # License along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 24 | # MA 02110-1301, USA. 25 | # 26 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 27 | # the x86 and x64 directories. Available from 28 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 29 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 30 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 31 | # and are registered in the United States and other countries. 32 | # All Rights Reserved. 33 | -------------------------------------------------------------------------------- /coverage-fixup.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import re 3 | import sqlite3 4 | 5 | conn = sqlite3.connect(".coverage") 6 | c = conn.cursor() 7 | 8 | old_base = "C:\\Users\\dom13" 9 | new_base = "/home/domdf" 10 | 11 | for (idx, filename) in c.execute("SELECT * FROM file").fetchall(): 12 | new_filename = re.sub(r"^\.tox/.*/site-packages/", "src/", filename) 13 | print(idx, filename, "->", new_filename) 14 | c.execute("UPDATE file SET path=? WHERE id=?", (new_filename, idx)) 15 | 16 | conn.commit() 17 | conn.close() 18 | -------------------------------------------------------------------------------- /doc-source/404.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | =============== 4 | 404 5 | =============== 6 | 7 | We looked everywhere but we couldn't find that page! 8 | 9 | .. image:: not-found.png 10 | :align: center 11 | 12 | Try using the links in the sidebar to find what you are looking for. 13 | -------------------------------------------------------------------------------- /doc-source/Source.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Downloading source code 3 | ========================= 4 | 5 | The ``pyms-nist-search`` source code is available on GitHub, 6 | and can be accessed from the following URL: https://github.com/domdfcoding/pynist 7 | 8 | If you have ``git`` installed, you can clone the repository with the following command: 9 | 10 | .. prompt:: bash 11 | 12 | git clone https://github.com/domdfcoding/pynist 13 | 14 | .. parsed-literal:: 15 | 16 | Cloning into 'pynist'... 17 | remote: Enumerating objects: 47, done. 18 | remote: Counting objects: 100% (47/47), done. 19 | remote: Compressing objects: 100% (41/41), done. 20 | remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 21 | Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. 22 | Resolving deltas: 100% (66/66), done. 23 | 24 | | Alternatively, the code can be downloaded in a 'zip' file by clicking: 25 | | :guilabel:`Clone or download` --> :guilabel:`Download Zip` 26 | 27 | .. figure:: git_download.png 28 | :alt: Downloading a 'zip' file of the source code. 29 | 30 | Downloading a 'zip' file of the source code 31 | 32 | 33 | Building from source 34 | ----------------------- 35 | 36 | The recommended way to build ``pyms-nist-search`` is to use `tox `_: 37 | 38 | .. prompt:: bash 39 | 40 | tox -e build 41 | 42 | The source and wheel distributions will be in the directory ``dist``. 43 | 44 | If you wish, you may also use `pep517.build `_ or another :pep:`517`-compatible build tool. 45 | -------------------------------------------------------------------------------- /doc-source/_static/style.css: -------------------------------------------------------------------------------- 1 | /* This file is managed by 'repo_helper'. Don't edit it directly. */ 2 | 3 | div.highlight { 4 | -moz-tab-size: 4; 5 | tab-size: 4; 6 | } 7 | 8 | .field-list dt, dl.simple dt { 9 | margin-top: 0.5rem; 10 | } 11 | 12 | div.versionchanged ul, div.versionremoved ul { 13 | margin-left: 20px; 14 | margin-top: 0; 15 | } 16 | 17 | .longtable.autosummary { 18 | width: 100%; 19 | } 20 | -------------------------------------------------------------------------------- /doc-source/_templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "!base.html" %} 3 | {% block extrahead %} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /doc-source/api.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | API Reference 3 | ============== 4 | 5 | .. automodule:: pyms_nist_search 6 | :no-members: 7 | 8 | 9 | :mod:`~pyms_nist_search.base` 10 | ---------------------------------- 11 | 12 | .. automodule:: pyms_nist_search.base 13 | :no-members: 14 | 15 | .. autoclass:: pyms_nist_search.base.NISTBase 16 | 17 | .. latex:clearpage:: 18 | 19 | :mod:`~pyms_nist_search.docker_engine` 20 | --------------------------------------- 21 | 22 | .. automodule:: pyms_nist_search.docker_engine 23 | 24 | 25 | :mod:`~pyms_nist_search.reference_data` 26 | --------------------------------------- 27 | 28 | .. automodule:: pyms_nist_search.reference_data 29 | :no-members: 30 | 31 | .. autoclass:: pyms_nist_search.reference_data.ReferenceData 32 | 33 | 34 | :mod:`~pyms_nist_search.search_result` 35 | --------------------------------------- 36 | 37 | .. automodule:: pyms_nist_search.search_result 38 | :no-members: 39 | 40 | .. autoclass:: pyms_nist_search.search_result.SearchResult 41 | :exclude-members: __repr__ 42 | 43 | 44 | .. latex:vspace:: 40px 45 | 46 | :mod:`~pyms_nist_search.utils` 47 | ----------------------------------- 48 | 49 | .. automodule:: pyms_nist_search.utils 50 | 51 | 52 | :mod:`~pyms_nist_search.win_engine` 53 | ----------------------------------- 54 | 55 | .. automodule:: pyms_nist_search.win_engine 56 | :no-members: 57 | 58 | .. autoclass:: pyms_nist_search.win_engine.Engine 59 | -------------------------------------------------------------------------------- /doc-source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This file is managed by 'repo_helper'. Don't edit it directly. 4 | 5 | # stdlib 6 | import os 7 | import re 8 | import sys 9 | 10 | # 3rd party 11 | from sphinx_pyproject import SphinxConfig 12 | 13 | sys.path.append('.') 14 | 15 | config = SphinxConfig(globalns=globals()) 16 | project = config["project"] 17 | author = config["author"] 18 | documentation_summary = config.description 19 | 20 | github_url = "https://github.com/{github_username}/{github_repository}".format_map(config) 21 | 22 | rst_prolog = f""".. |pkgname| replace:: pyms-nist-search 23 | .. |pkgname2| replace:: ``pyms-nist-search`` 24 | .. |browse_github| replace:: `Browse the GitHub Repository <{github_url}>`__ 25 | """ 26 | 27 | slug = re.sub(r'\W+', '-', project.lower()) 28 | release = version = config.version 29 | 30 | sphinx_builder = os.environ.get("SPHINX_BUILDER", "html").lower() 31 | todo_include_todos = int(os.environ.get("SHOW_TODOS", 0)) and sphinx_builder != "latex" 32 | 33 | intersphinx_mapping = { 34 | "python": ("https://docs.python.org/3/", None), 35 | "sphinx": ("https://www.sphinx-doc.org/en/stable/", None), 36 | } 37 | 38 | html_theme_options = { 39 | "light_css_variables": { 40 | "toc-title-font-size": "12pt", 41 | "toc-font-size": "12pt", 42 | "admonition-font-size": "12pt", 43 | }, 44 | "dark_css_variables": { 45 | "toc-title-font-size": "12pt", 46 | "toc-font-size": "12pt", 47 | "admonition-font-size": "12pt", 48 | }, 49 | } 50 | 51 | html_context = {} 52 | htmlhelp_basename = slug 53 | 54 | latex_documents = [("index", f'{slug}.tex', project, author, "manual")] 55 | man_pages = [("index", slug, project, [author], 1)] 56 | texinfo_documents = [("index", slug, project, author, slug, project, "Miscellaneous")] 57 | 58 | toctree_plus_types = set(config["toctree_plus_types"]) 59 | 60 | autodoc_default_options = { 61 | "members": None, # Include all members (methods). 62 | "special-members": None, 63 | "autosummary": None, 64 | "show-inheritance": None, 65 | "exclude-members": ','.join(config["autodoc_exclude_members"]), 66 | } 67 | 68 | latex_elements = { 69 | "printindex": "\\begin{flushleft}\n\\printindex\n\\end{flushleft}", 70 | "tableofcontents": "\\pdfbookmark[0]{\\contentsname}{toc}\\sphinxtableofcontents", 71 | } 72 | 73 | 74 | # Fix for pathlib issue with sphinxemoji on Python 3.9 and Sphinx 4.x 75 | def copy_asset_files(app, exc): 76 | # 3rd party 77 | from domdf_python_tools.compat import importlib_resources 78 | from sphinx.util.fileutil import copy_asset 79 | 80 | if exc: 81 | return 82 | 83 | asset_files = ["twemoji.js", "twemoji.css"] 84 | for path in asset_files: 85 | path_str = os.fspath(importlib_resources.files("sphinxemoji") / path) 86 | copy_asset(path_str, os.path.join(app.outdir, "_static")) 87 | 88 | 89 | def setup(app): 90 | # 3rd party 91 | from sphinx_toolbox.latex import better_header_layout 92 | from sphinxemoji import sphinxemoji 93 | 94 | app.connect("config-inited", lambda app, config: better_header_layout(config)) 95 | app.connect("build-finished", copy_asset_files) 96 | app.add_js_file("https://unpkg.com/twemoji@latest/dist/twemoji.min.js") 97 | app.add_js_file("twemoji.js") 98 | app.add_css_file("twemoji.css") 99 | app.add_transform(sphinxemoji.EmojiSubstitutions) 100 | -------------------------------------------------------------------------------- /doc-source/contributing.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Contributing 3 | ============== 4 | 5 | .. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md 6 | 7 | ``pyms-nist-search`` uses `tox `_ to automate testing and packaging, 8 | and `pre-commit `_ to maintain code quality. 9 | 10 | Install ``pre-commit`` with ``pip`` and install the git hook: 11 | 12 | .. prompt:: bash 13 | 14 | python -m pip install pre-commit 15 | pre-commit install 16 | 17 | 18 | Coding style 19 | -------------- 20 | 21 | `formate `_ is used for code formatting. 22 | 23 | It can be run manually via ``pre-commit``: 24 | 25 | .. prompt:: bash 26 | 27 | pre-commit run formate -a 28 | 29 | 30 | Or, to run the complete autoformatting suite: 31 | 32 | .. prompt:: bash 33 | 34 | pre-commit run -a 35 | 36 | 37 | Automated tests 38 | ------------------- 39 | 40 | Tests are run with ``tox`` and ``pytest``. 41 | To run tests for a specific Python version, such as Python 3.10: 42 | 43 | .. prompt:: bash 44 | 45 | tox -e py310 46 | 47 | 48 | To run tests for all Python versions, simply run: 49 | 50 | .. prompt:: bash 51 | 52 | tox 53 | 54 | 55 | Type Annotations 56 | ------------------- 57 | 58 | Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: 59 | 60 | .. prompt:: bash 61 | 62 | tox -e mypy 63 | 64 | 65 | 66 | Build documentation locally 67 | ------------------------------ 68 | 69 | The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: 70 | 71 | .. prompt:: bash 72 | 73 | tox -e docs 74 | -------------------------------------------------------------------------------- /doc-source/docutils.conf: -------------------------------------------------------------------------------- 1 | [restructuredtext parser] 2 | tab_width : 4 3 | -------------------------------------------------------------------------------- /doc-source/git_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/doc-source/git_download.png -------------------------------------------------------------------------------- /doc-source/index.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | PyMassSpec NIST Search 3 | ======================== 4 | 5 | .. start short_desc 6 | 7 | .. documentation-summary:: 8 | :meta: 9 | 10 | .. end short_desc 11 | 12 | .. only:: html 13 | 14 | .. image:: https://img.shields.io/appveyor/build/domdfcoding/pyms-nist-search/master?logo=appveyor 15 | :target: https://ci.appveyor.com/project/domdfcoding/pyms-nist-search/branch/master 16 | :alt: AppVeyor Windows Build Status 17 | 18 | .. start shields 19 | 20 | .. only:: html 21 | 22 | .. list-table:: 23 | :stub-columns: 1 24 | :widths: 10 90 25 | 26 | * - Docs 27 | - |docs| |docs_check| 28 | * - Tests 29 | - |actions_linux| |actions_windows| |coveralls| 30 | * - PyPI 31 | - |pypi-version| |supported-versions| |supported-implementations| |wheel| 32 | * - Activity 33 | - |commits-latest| |commits-since| |maintained| |pypi-downloads| 34 | * - QA 35 | - |codefactor| |actions_flake8| |actions_mypy| 36 | * - Other 37 | - |license| |language| |requires| 38 | 39 | .. |docs| rtfd-shield:: 40 | :project: pynist 41 | :alt: Documentation Build Status 42 | 43 | .. |docs_check| actions-shield:: 44 | :workflow: Docs Check 45 | :alt: Docs Check Status 46 | 47 | .. |actions_linux| actions-shield:: 48 | :workflow: Linux 49 | :alt: Linux Test Status 50 | 51 | .. |actions_windows| actions-shield:: 52 | :workflow: Windows 53 | :alt: Windows Test Status 54 | 55 | .. |actions_flake8| actions-shield:: 56 | :workflow: Flake8 57 | :alt: Flake8 Status 58 | 59 | .. |actions_mypy| actions-shield:: 60 | :workflow: mypy 61 | :alt: mypy status 62 | 63 | .. |requires| image:: https://dependency-dash.repo-helper.uk/github/domdfcoding/pynist/badge.svg 64 | :target: https://dependency-dash.repo-helper.uk/github/domdfcoding/pynist/ 65 | :alt: Requirements Status 66 | 67 | .. |coveralls| coveralls-shield:: 68 | :alt: Coverage 69 | 70 | .. |codefactor| codefactor-shield:: 71 | :alt: CodeFactor Grade 72 | 73 | .. |pypi-version| pypi-shield:: 74 | :project: pyms-nist-search 75 | :version: 76 | :alt: PyPI - Package Version 77 | 78 | .. |supported-versions| pypi-shield:: 79 | :project: pyms-nist-search 80 | :py-versions: 81 | :alt: PyPI - Supported Python Versions 82 | 83 | .. |supported-implementations| pypi-shield:: 84 | :project: pyms-nist-search 85 | :implementations: 86 | :alt: PyPI - Supported Implementations 87 | 88 | .. |wheel| pypi-shield:: 89 | :project: pyms-nist-search 90 | :wheel: 91 | :alt: PyPI - Wheel 92 | 93 | .. |license| github-shield:: 94 | :license: 95 | :alt: License 96 | 97 | .. |language| github-shield:: 98 | :top-language: 99 | :alt: GitHub top language 100 | 101 | .. |commits-since| github-shield:: 102 | :commits-since: v0.7.2 103 | :alt: GitHub commits since tagged version 104 | 105 | .. |commits-latest| github-shield:: 106 | :last-commit: 107 | :alt: GitHub last commit 108 | 109 | .. |maintained| maintained-shield:: 2025 110 | :alt: Maintenance 111 | 112 | .. |pypi-downloads| pypi-shield:: 113 | :project: pyms-nist-search 114 | :downloads: month 115 | :alt: PyPI - Downloads 116 | 117 | .. end shields 118 | 119 | 120 | PyMassSpec extension for searching mass spectra using NIST's Spectrum Search Engine 121 | 122 | PyMassSpec NIST Search is Free Software licensed under the 123 | `GNU Lesser General Public License Version 3 `_. 124 | 125 | A copy of the MassBank of North America database, in JSON, MSP and NIST Library formats, 126 | is included for the purposes of these tests. 127 | This library was created on 22 April 2020 using the "parse_mona_json.py" script and Lib2Nist. 128 | Licensed under the CC BY 4.0 License. For a list of contributors, see the file ``MoNA_GCMS_Library/AUTHORS``. 129 | 130 | .. TODO: add links. 131 | 132 | Installation 133 | -------------- 134 | 135 | .. start installation 136 | 137 | .. installation:: pyms-nist-search 138 | :pypi: 139 | :github: 140 | 141 | .. end installation 142 | 143 | Contents 144 | ------------ 145 | 146 | .. html-section:: 147 | 148 | .. toctree:: 149 | :hidden: 150 | 151 | Home 152 | 153 | 154 | .. toctree:: 155 | :maxdepth: 3 156 | 157 | usage 158 | api 159 | contributing 160 | license 161 | Source 162 | 163 | .. sidebar-links:: 164 | :caption: Links 165 | :github: 166 | :pypi: pyms-nist-search 167 | 168 | 169 | .. start links 170 | 171 | .. only:: html 172 | 173 | View the :ref:`Function Index ` or browse the `Source Code <_modules/index.html>`__. 174 | 175 | :github:repo:`Browse the GitHub Repository ` 176 | 177 | .. end links 178 | -------------------------------------------------------------------------------- /doc-source/license.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | License 3 | ========= 4 | 5 | ``pyms-nist-search`` is licensed under the :choosealicense:`LGPL-3.0` 6 | 7 | .. license-info:: LGPL-3.0 8 | 9 | .. license:: 10 | :py: pyms-nist-search 11 | -------------------------------------------------------------------------------- /doc-source/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/doc-source/not-found.png -------------------------------------------------------------------------------- /doc-source/requirements.txt: -------------------------------------------------------------------------------- 1 | default-values>=0.6.0 2 | extras-require>=0.5.0 3 | furo==2021.06.18b36 4 | html-section>=0.3.0 5 | seed-intersphinx-mapping>=1.2.2 6 | sphinx>=3.0.3 7 | sphinx-copybutton>=0.2.12 8 | sphinx-debuginfo>=0.2.2 9 | sphinx-licenseinfo>=0.3.1 10 | sphinx-notfound-page>=0.7.1 11 | sphinx-pyproject>=0.1.0 12 | sphinx-toolbox>=3.5.0 13 | sphinxcontrib-applehelp==1.0.4 14 | sphinxcontrib-devhelp==1.0.2 15 | sphinxcontrib-htmlhelp==2.0.1 16 | sphinxcontrib-httpdomain>=1.7.0 17 | sphinxcontrib-jsmath==1.0.1 18 | sphinxcontrib-qthelp==1.0.3 19 | sphinxcontrib-serializinghtml==1.1.5 20 | sphinxemoji>=0.2.0 21 | toctree-plus>=0.6.1 22 | -------------------------------------------------------------------------------- /doc-source/usage.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Usage 3 | ======= 4 | 5 | You will need to supply your own copy of the NIST Mass Spectral library to use this software. 6 | 7 | The main class in this library is the :class:`~pyms_nist_search.win_engine.Engine` class. This class performs the actual searching. 8 | Start by initialising the search engine as follows: 9 | 10 | .. code-block:: python 11 | 12 | search = pyms_nist_search.Engine( 13 | FULL_PATH_TO_MAIN_LIBRARY, 14 | pyms_nist_search.NISTMS_MAIN_LIB, 15 | FULL_PATH_TO_WORK_DIR, 16 | ) 17 | 18 | Where ``FULL_PATH_TO_MAIN_LIBRARY`` is the path to the location of your mass spectral library, 19 | and ``FULL_PATH_TO_WORK_DIR`` is the path to the working directory to be used by the search engine. 20 | 21 | A :class:`pyms.Spectrum.MassSpectrum` object can then be searched as follows: 22 | 23 | .. code-block:: python 24 | 25 | search.full_search_with_ref_data(mass_spec) 26 | 27 | This will return a list of tuples consisting of :class:`~.SearchResult` and :class:`~.ReferenceData` 28 | objects for the possible identities of the mass spectrum. 29 | 30 | A list of just the :class:`~.SearchResult` objects can be obtained with this method: 31 | 32 | .. code-block:: python 33 | 34 | hit_list = search.full_search(mass_spec) 35 | 36 | For each of these hits, the reference data can be obtained as follows: 37 | 38 | .. code-block:: python 39 | 40 | for hit in hit_list: 41 | ref_data = search.get_reference_data(hit.spec_loc) 42 | -------------------------------------------------------------------------------- /formate.toml: -------------------------------------------------------------------------------- 1 | [hooks] 2 | dynamic_quotes = 10 3 | collections-import-rewrite = 20 4 | reformat-generics = 40 5 | noqa-reformat = 60 6 | ellipsis-reformat = 70 7 | squish_stubs = 80 8 | 9 | [hooks.yapf] 10 | priority = 30 11 | 12 | [hooks.yapf.kwargs] 13 | yapf_style = ".style.yapf" 14 | 15 | [hooks.isort] 16 | priority = 50 17 | 18 | [hooks.isort.kwargs] 19 | indent = " " 20 | multi_line_output = 8 21 | import_heading_stdlib = "stdlib" 22 | import_heading_thirdparty = "3rd party" 23 | import_heading_firstparty = "this package" 24 | import_heading_localfolder = "this package" 25 | balanced_wrapping = false 26 | lines_between_types = 0 27 | use_parentheses = true 28 | remove_redundant_aliases = true 29 | default_section = "THIRDPARTY" 30 | known_third_party = [ 31 | "cftime", 32 | "chemistry_tools", 33 | "coincidence", 34 | "coverage", 35 | "coverage_pyver_pragma", 36 | "docker", 37 | "domdf_python_tools", 38 | "github", 39 | "importlib_metadata", 40 | "jinja2", 41 | "numpy", 42 | "pymassspec", 43 | "pytest", 44 | "pytest_cov", 45 | "pytest_randomly", 46 | "pytest_rerunfailures", 47 | "pytest_timeout", 48 | "requests", 49 | "sdjson", 50 | ] 51 | known_first_party = [ "pyms_nist_search",] 52 | 53 | [config] 54 | indent = " " 55 | line_length = 115 56 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: lint 2 | 3 | pdf-docs: latex-docs 4 | make -C doc-source/build/latex/ 5 | 6 | latex-docs: 7 | SPHINX_BUILDER=latex tox -e docs 8 | 9 | unused-imports: 10 | tox -e lint -- --select F401 11 | 12 | incomplete-defs: 13 | tox -e lint -- --select MAN 14 | 15 | vdiff: 16 | git diff $(repo-helper show version -q)..HEAD 17 | 18 | bare-ignore: 19 | greppy '# type:? *ignore(?!\[|\w)' -s 20 | 21 | lint: unused-imports incomplete-defs bare-ignore 22 | tox -n qa 23 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ "setuptools!=61.*,<=67.1.0,>=40.6.0", "wheel>=0.34.2",] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pyms-nist-search" 7 | version = "0.7.2" 8 | description = "PyMassSpec extension for searching mass spectra using NIST's Mass Spectrum Search Engine." 9 | readme = "README.rst" 10 | requires-python = ">=3.7" 11 | keywords = [] 12 | classifiers = [ 13 | "Development Status :: 4 - Beta", 14 | "Intended Audience :: Developers", 15 | "Intended Audience :: Science/Research", 16 | "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", 17 | "Operating System :: Microsoft :: Windows", 18 | "Operating System :: Microsoft :: Windows :: Windows 7", 19 | "Operating System :: Microsoft :: Windows :: Windows 8.1", 20 | "Operating System :: Microsoft :: Windows :: Windows 10", 21 | "Operating System :: POSIX :: Linux", 22 | "Programming Language :: C", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 3 :: Only", 25 | "Programming Language :: Python :: 3.7", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | "Programming Language :: Python :: 3.12", 31 | "Programming Language :: Python :: Implementation :: CPython", 32 | "Topic :: Scientific/Engineering :: Chemistry", 33 | "Topic :: Software Development :: Libraries :: Python Modules", 34 | "Topic :: Utilities", 35 | "Typing :: Typed", 36 | ] 37 | dynamic = [ "dependencies",] 38 | 39 | [project.license] 40 | file = "LICENSE" 41 | 42 | [[project.authors]] 43 | name = "Dominic Davis-Foster" 44 | email = "dominic@davis-foster.co.uk" 45 | 46 | [project.urls] 47 | Homepage = "https://github.com/domdfcoding/pynist" 48 | "Issue Tracker" = "https://github.com/domdfcoding/pynist/issues" 49 | "Source Code" = "https://github.com/domdfcoding/pynist" 50 | Documentation = "https://pynist.readthedocs.io/en/latest" 51 | 52 | [tool.whey] 53 | base-classifiers = [ 54 | "Development Status :: 4 - Beta", 55 | "Intended Audience :: Developers", 56 | "Intended Audience :: Science/Research", 57 | "Operating System :: Microsoft :: Windows :: Windows 7", 58 | "Operating System :: Microsoft :: Windows :: Windows 8.1", 59 | "Operating System :: Microsoft :: Windows :: Windows 10", 60 | "Programming Language :: C", 61 | "Topic :: Scientific/Engineering :: Chemistry", 62 | "Topic :: Software Development :: Libraries :: Python Modules", 63 | "Topic :: Utilities", 64 | "Typing :: Typed", 65 | ] 66 | python-versions = [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12",] 67 | python-implementations = [ "CPython",] 68 | platforms = [ "Windows", "Linux",] 69 | license-key = "LGPL-3.0-or-later" 70 | package = "pyms_nist_search" 71 | additional-files = [ 72 | "include THIRD_PARTY_COPYRIGHT", 73 | "recursive-include src/pyms_nist_search *", 74 | "recursive-exclude MoNA_GCMS_Library *", 75 | "recursive-exclude src/pyms_nist_search *.so", 76 | "recursive-include src/pyms_nist_search/templates *", 77 | "recursive-include src/pyms_nist_search *.c", 78 | "recursive-include src/pyms_nist_search *.C", 79 | "recursive-include src/pyms_nist_search *.h", 80 | "recursive-include src/pyms_nist_search *.H", 81 | "recursive-include src/pyms_nist_search *.dll", 82 | "recursive-include src/pyms_nist_search *.lib", 83 | ] 84 | 85 | [tool.sphinx-pyproject] 86 | github_username = "domdfcoding" 87 | github_repository = "pynist" 88 | author = "Dominic Davis-Foster" 89 | project = "pyms-nist-search" 90 | copyright = "2020-2021 Dominic Davis-Foster" 91 | language = "en" 92 | package_root = "pyms_nist_search" 93 | extensions = [ 94 | "sphinx_toolbox", 95 | "sphinx_toolbox.more_autodoc", 96 | "sphinx_toolbox.more_autosummary", 97 | "sphinx_toolbox.documentation_summary", 98 | "sphinx_toolbox.tweaks.param_dash", 99 | "sphinxcontrib.toctree_plus", 100 | "sphinx_toolbox.tweaks.latex_layout", 101 | "sphinx_toolbox.tweaks.latex_toc", 102 | "sphinx.ext.intersphinx", 103 | "sphinx.ext.mathjax", 104 | "sphinxcontrib.extras_require", 105 | "sphinx.ext.todo", 106 | "notfound.extension", 107 | "sphinx_copybutton", 108 | "sphinxcontrib.default_values", 109 | "sphinx_debuginfo", 110 | "sphinx_licenseinfo", 111 | "seed_intersphinx_mapping", 112 | "html_section", 113 | ] 114 | gitstamp_fmt = "%d %b %Y" 115 | templates_path = [ "_templates",] 116 | html_static_path = [ "_static",] 117 | source_suffix = ".rst" 118 | master_doc = "index" 119 | suppress_warnings = [ "image.nonlocal_uri",] 120 | pygments_style = "default" 121 | html_theme = "furo" 122 | html_theme_path = [ "../..",] 123 | html_show_sourcelink = true 124 | toctree_plus_types = [ 125 | "class", 126 | "confval", 127 | "data", 128 | "directive", 129 | "enum", 130 | "exception", 131 | "flag", 132 | "function", 133 | "namedtuple", 134 | "protocol", 135 | "role", 136 | "typeddict", 137 | ] 138 | add_module_names = false 139 | hide_none_rtype = true 140 | all_typevars = true 141 | overloads_location = "bottom" 142 | html_codeblock_linenos_style = "table" 143 | autodoc_exclude_members = [ 144 | "__dict__", 145 | "__class__", 146 | "__dir__", 147 | "__weakref__", 148 | "__module__", 149 | "__annotations__", 150 | "__orig_bases__", 151 | "__parameters__", 152 | "__subclasshook__", 153 | "__init_subclass__", 154 | "__attrs_attrs__", 155 | "__init__", 156 | "__new__", 157 | "__getnewargs__", 158 | "__abstractmethods__", 159 | "__hash__", 160 | ] 161 | 162 | [tool.mypy] 163 | python_version = "3.8" 164 | namespace_packages = true 165 | check_untyped_defs = true 166 | warn_unused_ignores = true 167 | no_implicit_optional = true 168 | show_error_codes = true 169 | 170 | [tool.snippet-fmt] 171 | directives = [ "code-block",] 172 | 173 | [tool.snippet-fmt.languages.python] 174 | reformat = true 175 | 176 | [tool.snippet-fmt.languages.TOML] 177 | reformat = true 178 | 179 | [tool.snippet-fmt.languages.ini] 180 | 181 | [tool.snippet-fmt.languages.json] 182 | 183 | [tool.dep_checker] 184 | allowed_unused = [ "cftime",] 185 | 186 | [tool.dep_checker.name_mapping] 187 | pymassspec = "pyms" 188 | 189 | [tool.setuptools] 190 | zip-safe = false 191 | include-package-data = true 192 | platforms = [ "Windows", "macOS", "Linux",] 193 | 194 | [tool.dependency-dash."requirements.txt"] 195 | order = 10 196 | 197 | [tool.dependency-dash."tests/requirements.txt"] 198 | order = 20 199 | include = false 200 | 201 | [tool.dependency-dash."doc-source/requirements.txt"] 202 | order = 30 203 | include = false 204 | -------------------------------------------------------------------------------- /read_jcamp.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import pathlib 3 | 4 | # this package 5 | from pyms_nist_search import ReferenceData 6 | 7 | file_name = pathlib.Path("/home/domdf/Python/01 GitHub Repos/pynist/tests/test_spectra/71-43-2-Mass.jdx") 8 | 9 | print(ReferenceData.from_jcamp(file_name)) 10 | -------------------------------------------------------------------------------- /repo_helper.yml: -------------------------------------------------------------------------------- 1 | modname: pyms-nist-search 2 | import_name: pyms_nist_search 3 | repo_name: pynist 4 | copyright_years: "2020-2021" 5 | author: "Dominic Davis-Foster" 6 | email: "dominic@davis-foster.co.uk" 7 | version: "0.7.2" 8 | username: "domdfcoding" 9 | license: 'LGPLv3+' 10 | short_desc: "PyMassSpec extension for searching mass spectra using NIST's Mass Spectrum Search Engine." 11 | 12 | enable_conda: False 13 | 14 | exclude_files: 15 | - setup 16 | 17 | platforms: 18 | - Windows 19 | - Linux 20 | 21 | python_deploy_version: 3.8 22 | mypy_version: "1.8.0" 23 | 24 | # Versions to run tests for 25 | python_versions: 26 | - '3.7' 27 | - '3.8' 28 | - '3.9' 29 | - '3.10' 30 | - '3.11' 31 | - '3.12' 32 | 33 | # additional lines for MANIFEST.in 34 | manifest_additional: 35 | - "include THIRD_PARTY_COPYRIGHT" 36 | - "recursive-include src/pyms_nist_search *" 37 | - "recursive-exclude MoNA_GCMS_Library *" 38 | - "recursive-exclude src/pyms_nist_search *.so" 39 | - "recursive-include src/pyms_nist_search/templates *" 40 | - "recursive-include src/pyms_nist_search *.c" 41 | - "recursive-include src/pyms_nist_search *.C" 42 | - "recursive-include src/pyms_nist_search *.h" 43 | - "recursive-include src/pyms_nist_search *.H" 44 | - "recursive-include src/pyms_nist_search *.dll" 45 | - "recursive-include src/pyms_nist_search *.lib" 46 | 47 | classifiers: 48 | - 'Development Status :: 4 - Beta' 49 | - 'Intended Audience :: Developers' 50 | - 'Topic :: Utilities' 51 | - "Operating System :: Microsoft :: Windows :: Windows 10" 52 | - "Operating System :: Microsoft :: Windows :: Windows 7" 53 | - "Operating System :: Microsoft :: Windows :: Windows 8.1" 54 | - "Intended Audience :: Developers" 55 | - "Intended Audience :: Science/Research" 56 | - "Programming Language :: C" 57 | # - "Topic :: Database :: Front-Ends" 58 | - "Topic :: Scientific/Engineering :: Chemistry" 59 | - "Topic :: Software Development :: Libraries :: Python Modules" 60 | 61 | additional_ignore: 62 | # NIST Library 63 | - "*/mainlib" 64 | - "**/.vagrant" 65 | - "**/old" 66 | - "/src/pyms_nist_search/constants.h" 67 | - "_core.cpython*" 68 | - "**/nist_jdx_files" 69 | - "**/MoNA-export-GC-MS_Spectra.json" 70 | 71 | source_dir: src 72 | pure_python: False 73 | standalone_contrib_guide: true 74 | sphinx_html_theme: furo 75 | 76 | extra_lint_paths: 77 | - MoNA_GCMS_Library 78 | 79 | extra_testenv_commands: 80 | - python coverage-fixup.py 81 | 82 | tox_unmanaged: 83 | - testenv 84 | 85 | github_ci_requirements: 86 | Linux: 87 | post: 88 | - sudo add-apt-repository universe 89 | - sudo apt update 90 | - sudo apt install libhdf5-dev netcdf-bin libnetcdf-dev 91 | Windows: 92 | post: 93 | # Prefery binary wheels for older versions over sdists for newer ones 94 | - pip config set global.prefer-binary true 95 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # TODO: Numpy is broken in Windows 2004 except for 1.19.3+ 2 | # A fix should be out by end of Jan 2021 🙈 3 | # https://developercommunity.visualstudio.com/content/problem/1207405/fmod-after-an-update-to-windows-2004-is-causing-a.html 4 | cftime<=1.3.0; python_version == "3.6" and platform_system == "Windows" 5 | chemistry-tools>=0.2.5 6 | docker>=4.2.0 7 | domdf-python-tools>=3.3.0 8 | jinja2>=2.11.3 9 | numpy>=1.19.1; platform_system != "Windows" 10 | numpy!=1.19.4,>=1.19.3; platform_system == "Windows" 11 | pymassspec>=2.2.20 12 | requests>=2.22.0 13 | sdjson>=0.2.6 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. 2 | # You may add new sections, but any changes made to the following sections will be lost: 3 | # * metadata 4 | # * options 5 | # * options.packages.find 6 | # * mypy 7 | # * options.entry_points 8 | 9 | [metadata] 10 | name = pyms-nist-search 11 | version = 0.7.2 12 | author = Dominic Davis-Foster 13 | author_email = dominic@davis-foster.co.uk 14 | license = GNU Lesser General Public License v3 or later (LGPLv3+) 15 | keywords = 16 | long_description = file: README.rst 17 | long_description_content_type = text/x-rst 18 | platforms = Windows, Linux 19 | url = https://github.com/domdfcoding/pynist 20 | project_urls = 21 | Documentation = https://pynist.readthedocs.io/en/latest 22 | Issue Tracker = https://github.com/domdfcoding/pynist/issues 23 | Source Code = https://github.com/domdfcoding/pynist 24 | classifiers = 25 | Development Status :: 4 - Beta 26 | Intended Audience :: Developers 27 | Intended Audience :: Science/Research 28 | License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) 29 | Operating System :: Microsoft :: Windows 30 | Operating System :: Microsoft :: Windows :: Windows 7 31 | Operating System :: Microsoft :: Windows :: Windows 8.1 32 | Operating System :: Microsoft :: Windows :: Windows 10 33 | Operating System :: POSIX :: Linux 34 | Programming Language :: C 35 | Programming Language :: Python 36 | Programming Language :: Python :: 3 :: Only 37 | Programming Language :: Python :: 3.7 38 | Programming Language :: Python :: 3.8 39 | Programming Language :: Python :: 3.9 40 | Programming Language :: Python :: 3.10 41 | Programming Language :: Python :: 3.11 42 | Programming Language :: Python :: 3.12 43 | Programming Language :: Python :: Implementation :: CPython 44 | Topic :: Scientific/Engineering :: Chemistry 45 | Topic :: Software Development :: Libraries :: Python Modules 46 | Topic :: Utilities 47 | Typing :: Typed 48 | 49 | [options] 50 | python_requires = >=3.7 51 | zip_safe = False 52 | include_package_data = True 53 | packages = find: 54 | 55 | [options.packages.find] 56 | exclude = 57 | doc-source 58 | tests 59 | tests.* 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # stdlib 4 | import pathlib 5 | import shutil 6 | import sys 7 | 8 | # 3rd party 9 | from setuptools import Extension, setup 10 | 11 | sys.path.append('.') 12 | 13 | common_kwargs = dict( 14 | description="PyMassSpec extension for searching mass spectra using NIST's Mass Spectrum Search Engine.", 15 | packages=["pyms_nist_search"], 16 | package_dir={"pyms_nist_search": "src/pyms_nist_search"}, 17 | # package_data={modname: ["pyms_nist_search/templates/*"]}, 18 | ) 19 | 20 | docker_only_reqs = [ 21 | "docker>=4.2.0", 22 | "requests>=2.22.0", 23 | ] 24 | 25 | build_macros = [ 26 | ("INTERNALBUILD", "1"), 27 | ("WIN32", "1"), 28 | ("MSTXTDATA", "1"), 29 | ] 30 | 31 | 32 | extras_require = {} 33 | 34 | repo_root = pathlib.Path(__file__).parent 35 | install_requires = (repo_root / "requirements.txt").read_text(encoding="UTF-8").split('\n') 36 | 37 | ############################## 38 | 39 | 40 | if sys.platform == "win32": 41 | 42 | if sys.maxsize > 2 ** 32: 43 | x = 64 44 | bit = 64 45 | ctree = "ctNt66_64" 46 | # libraries = ["src/pyms_nist_search/x64/nistdl64'] 47 | # data_files = [('', ['src/pyms_nist_search/x64/nistdl64.dll', 'src/pyms_nist_search/x64/ctNt66_64.dll'])] 48 | else: 49 | x = 86 50 | bit = 32 51 | ctree = "ctNt66" 52 | # libraries = ['src/pyms_nist_search/x86/nistdl32'] 53 | # data_files = [('', ['src/pyms_nist_search/x86/nistdl32.dll', 'src/pyms_nist_search/x86/ctNt66.dll'])] 54 | libraries = [f'src/pyms_nist_search/x{x}/nistdl{bit}'] 55 | data_files = [('', [f'src/pyms_nist_search/x{x}/nistdl{bit}.dll', f'src/pyms_nist_search/x{x}/{ctree}.dll'])] 56 | 57 | extension = Extension( 58 | name='pyms_nist_search._core', 59 | define_macros=build_macros, 60 | libraries=libraries, 61 | sources=['src/pyms_nist_search/pyms_nist_search.c'], 62 | include_dirs=["src/pyms_nist_search"], 63 | language="c", 64 | ) 65 | 66 | setup( 67 | **common_kwargs, 68 | install_requires=[req for req in install_requires if req not in docker_only_reqs], 69 | ext_modules=[extension], 70 | data_files=data_files 71 | ) 72 | 73 | else: 74 | # On platforms other than windows, build the minimal C extension that just contains the variables, 75 | # as well as the Python files that are required for cross compatibility. 76 | 77 | min_extension = Extension( 78 | name='pyms_nist_search._core', 79 | define_macros=build_macros, 80 | sources=['src/pyms_nist_search/pyms_nist_search_min.c'], 81 | include_dirs=["src/pyms_nist_search"], 82 | ) 83 | 84 | setup( 85 | **common_kwargs, 86 | install_requires=install_requires, 87 | ext_modules=[min_extension], 88 | ) 89 | 90 | 91 | shutil.rmtree("pyms_nist_search.egg-info", ignore_errors=True) 92 | -------------------------------------------------------------------------------- /src/pyms_nist_search/NISTERR.H: -------------------------------------------------------------------------------- 1 | /* 2 | NISTERR.H 3 | 4 | C Interface to the NIST MS Search DLL 5 | 6 | From ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip 7 | 8 | */ 9 | 10 | /* %Z%%M% %I% %G% RELEASE: %R% TFLAG %Y% */ 11 | /* from WGET_ID.C */ 12 | #define ERR_OUT_OF_RAM -1 13 | #define ERROR_NOT_USER_LIB -2 14 | #define ERR_CANNOT_INIT_CTREEGV -3 15 | #define ERR_CANNOT_INIT_CTREE -4 16 | #define ERR_CANNOT_GET_USER_PATH -5 17 | #define ERR_TOO_MANY_PEAKS -6 18 | #define ERR_CANNOT_LOAD_NAME -7 19 | #define ERR_CANNOT_OPEN_NAMEFILE -8 20 | #define ERR_CANNOT_SETUP_PATHS -9 21 | #define ERR_CANNOT_OPEN_LIB_FILES -10 22 | #define ERR_UNRECOGNIZED_SEARCH_TYPE -11 23 | #define ERR_COULD_NOT_READ_PEAKS -12 24 | #define ERR_LINE_TOO_LONG -13 25 | #define ERR_BUFFERS_NOT_ALLOCATED -14 26 | #define ERR_SEARCH_REENTRY -15 /* attempt to call search while another search is in progress */ 27 | #define ERR_WRONG_ACTIVE_LIB_COUNT -16 /* added 2010-10-08 */ 28 | #define ERR_WRONG_LIBRARY -17 /* added 2010-10-08 */ 29 | 30 | /* from NISTMS.H */ 31 | #define ERR_CANNOT_SETUP_DBS -101 32 | #define ERR_BAD_INPUT_DATA -102 33 | #define ERR_BAD_SEARCH_LIBS -103 34 | #define ERR_CANNOT_SET_ACTIVE_LIBS -104 35 | #define ERR_MKDIR -105 36 | #define ERR_LIB_ALREADY_EXIST -106 37 | #define ERR_CANNOT_CREATE_LIB -107 38 | #define ERR_READING_SPEC -108 /* 2-10-99 DCh */ 39 | #define ERR_CONSTRAINT_SYNTAX -109 /* 4-13-06 DCh */ 40 | 41 | /* WGET_ANY.C */ 42 | #define ERR1 -201 /* out of RAM A */ 43 | #define ERR2 -202 /* out of RAM B */ 44 | #define ERR3 -203 /* out of RAM C */ 45 | #define ERR4 -204 /* current peak values out of range */ 46 | #define ERR5 -205 /* selected delete with no peaks */ 47 | #define ERR6 -206 /* problem deleting peak */ 48 | #define ERR7 -207 /* asked for hits without providing peaks */ 49 | #define ERR8 -208 /* too many allowable misses */ 50 | #define ERR9 -209 /* problem finding hit id numbers */ 51 | #define ERR10 -210 /* problem in find all */ 52 | #define ERR11 -211 /* cannot open peakdb file */ 53 | #define ERR12 -212 /* error in sorting */ 54 | #define ERR13 -213 /* cannot open or read PEAK.DB file */ 55 | #define ERR14 -214 /* error in screen search counting */ 56 | #define ERR15 -215 /* too many peaks requested; peak ignored */ 57 | #define NONEFOUND 2 /* no hits found */ 58 | 59 | /* WGET_AUT.C */ 60 | #define ERR_AUT_OUT_OF_RAM -301 61 | #define ERR_CANNOT_FIND_PEAKS -302 62 | #define ERR_PROBLEM_GETTING_MATCHES -303 63 | #define WRN_NO_SPECTRA_TO_COMPARE -304 64 | #define WRN_TOO_MANY_HITS -305 65 | #define WRN_COULD_NOT_FIND_IDKEY -306 66 | 67 | /* MAINMSDB.C */ 68 | #define ERR_BAD_DBNUM -400 69 | #define ERR_SEARCH_LIBS_STRING -401 70 | #define ERR_NOT_A_USERLIB -402 71 | #define ERR_MAINLIB_NOT_INSTALLED -403 72 | #define ERR_REPLIB_NOT_INSTALLED -404 73 | #define ERR_WRONG_NUM_LIBS -405 74 | #define ERR_WRONG_ACTIVE_LIBS -406 75 | #define ERR_NOT_INITIALIZED -407 76 | #define ERR_CANNOT_REINITIALIZE -408 77 | #define ERR_NO_NAME_PROVIDED -409 78 | #define ERR_MAX_HITS_DESIRED_TOO_SMALL -409 79 | #define ERR_CANNOT_ALLOCATE_STRUBUF -410 80 | #define ERR_BAD_MOLFILE_HANDLE -411 81 | 82 | /* WGET_NAM.C */ 83 | #define ERR_NAM_OUT_OF_RAM -501 84 | 85 | /* WGET_INP.C */ 86 | #define ERR_CANNOT_OPEN_INPUT_FILE -600 87 | #define ERR_PARSING_INPUT_FILE -601 88 | #define ERR_COULD_NOT_FIND_NUM_PEAKS -602 89 | #define ERR_CANNOT_FIND_ASCII_SPECTRUM -603 90 | #define ERR_CANNOT_ALLOC_ABUND -604 91 | 92 | #define ERR_MAX_SPEC_LOCS_TOO_SMALL -605 93 | 94 | #define WRN_SEARCH_ABORTED -606 /* CallBack returned Cancel. */ 95 | -------------------------------------------------------------------------------- /src/pyms_nist_search/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | """ 5 | PyMassSpec extension for searching mass spectra using NIST's Mass Spectrum Search Engine. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2022 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | # 36 | 37 | # stdlib 38 | import os 39 | import pathlib 40 | import platform 41 | import sys 42 | 43 | if sys.platform == "win32": 44 | if sys.version_info < (3, 8): 45 | 46 | python_base_dir = pathlib.Path(__file__).parent.parent.parent.parent 47 | # assert (python_base_dir / "nistdl64.dll").is_file() 48 | # assert (python_base_dir / "ctNt66_64.dll").is_file() 49 | if not str(python_base_dir.absolute()) in os.environ["PATH"].split(':'): 50 | os.environ["PATH"] += os.pathsep + str(python_base_dir.absolute()) 51 | del python_base_dir 52 | 53 | else: 54 | _64_bit = platform.architecture()[0] == "64bit" 55 | os.add_dll_directory(os.path.join(os.path.split(__file__)[0], "x64" if _64_bit else "x86")) 56 | 57 | # this package 58 | from pyms_nist_search.reference_data import ReferenceData # noqa: F401 59 | from pyms_nist_search.search_result import SearchResult # noqa: F401 60 | 61 | # this package 62 | from ._core import * # noqa: F401 63 | 64 | if sys.platform == "win32": 65 | # this package 66 | from pyms_nist_search.win_engine import Engine 67 | 68 | else: 69 | # this package 70 | from pyms_nist_search.docker_engine import Engine # noqa: F401 71 | 72 | name: str = "PyMassSpec NIST Search" 73 | __author__: str = "Dominic Davis-Foster" 74 | __license__: str = "LGPLv3+" 75 | __maintainer_email__: str = "dominic@davis-foster.co.uk" 76 | __version__: str = "0.7.2" 77 | 78 | __copyright__: str = "2020 Dominic Davis-Foster" 79 | __email__: str = "dominic@davis-foster.co.uk" 80 | -------------------------------------------------------------------------------- /src/pyms_nist_search/__init__.pyi: -------------------------------------------------------------------------------- 1 | # this package 2 | from pyms_nist_search.docker_engine import Engine as Engine # noqa: F401 3 | from pyms_nist_search.reference_data import ReferenceData as ReferenceData # noqa: F401 4 | from pyms_nist_search.search_result import SearchResult as SearchResult # noqa: F401 5 | 6 | name: str 7 | __author__: str 8 | __license__: str 9 | __maintainer_email__: str 10 | __version__: str 11 | 12 | __copyright__: str 13 | __email__: str 14 | 15 | COLHDRLEN: int 16 | COUNT_REF_PEAKS: int 17 | EXACTMW_CONS: int 18 | INSTR_TYPE_CONS: int 19 | INTERNALBUILD: int 20 | MAX_LIB_SRCH_HITS: int 21 | MAX_NOPRESRCH_HITS: int 22 | MAX_WARN_STR: int 23 | MSTXTDATA: int 24 | NISTMS_ABS_PEAKS: int 25 | NISTMS_ADD_TO_LIBRARY_SRCH: int 26 | NISTMS_ALT2AROM: int 27 | NISTMS_AM2_PEAK: int 28 | NISTMS_ANYPEAK_GET_HITS_SRCH: int 29 | NISTMS_ANYPEAK_INIT_SRCH: int 30 | NISTMS_ANYPEAK_ONE_PEAK_SRCH: int 31 | NISTMS_ANY_PEAK: int 32 | NISTMS_BIT_INSTR_TYPE_IONTRAP: int 33 | NISTMS_BIT_INSTR_TYPE_NONE: int 34 | NISTMS_BIT_INSTR_TYPE_OTHER: int 35 | NISTMS_BIT_INSTR_TYPE_QQQ: int 36 | NISTMS_BIT_INSTR_TYPE_QTOF: int 37 | NISTMS_CASNO_SRCH: int 38 | NISTMS_CASNO_SRCH2: int 39 | NISTMS_CLOSE_MOLFILE: int 40 | NISTMS_CLOSE_SRCH: int 41 | NISTMS_CL_BR_EST: int 42 | NISTMS_COMMNT_TAG_LEN: int 43 | NISTMS_COMPARE_SPECTRA_SRCH: int 44 | NISTMS_CREATE_LIBRARY: int 45 | NISTMS_DECODE_MODS: int 46 | NISTMS_DELETE_FROM_LIBRARY_SRCH: int 47 | NISTMS_DFLT_MAX_PEAK_TXTDATA_LEN: int 48 | NISTMS_DFLT_MAX_PEAK_TXTDATA_NUM: int 49 | NISTMS_ELS_IN_LIST: int 50 | NISTMS_EXACT: int 51 | NISTMS_EXACT_MASS_PEAK: int 52 | NISTMS_F32_VALUE_075: int 53 | NISTMS_F32_VALUE_ONE: int 54 | NISTMS_FAKE_PREC_MZ: int 55 | NISTMS_FORMULA_SRCH: int 56 | NISTMS_GET_SPECTRUM_SRCH: int 57 | NISTMS_GET_STRUCTURE_SRCH: int 58 | NISTMS_GET_SYNONYMS_SRCH: int 59 | NISTMS_ID_SRCH: int 60 | NISTMS_INC_FIRST_NAME_SRCH: int 61 | NISTMS_INC_GET_NAME_KEY: int 62 | NISTMS_INC_NEXT_NAME_SRCH: int 63 | NISTMS_INC_PREV_NAME_SRCH: int 64 | NISTMS_INDEX_LIBRARY_NAMES: int 65 | NISTMS_INDEX_USER_STRU: int 66 | NISTMS_INIT_SRCH: int 67 | NISTMS_INSTR_TYPE_IONTRAP: int 68 | NISTMS_INSTR_TYPE_MASK: int 69 | NISTMS_INSTR_TYPE_NONE: int 70 | NISTMS_INSTR_TYPE_QQQ: int 71 | NISTMS_INSTR_TYPE_QTOF: int 72 | NISTMS_INSTR_TYPE_UNK: int 73 | NISTMS_LEN_CONSTR_EL: int 74 | NISTMS_LOSS_PEAK: int 75 | NISTMS_MAIN_LIB: int 76 | NISTMS_MAKE_MOLFILE: int 77 | NISTMS_MARK_ALL_LIBS: int 78 | NISTMS_MARK_LIBS: int 79 | NISTMS_MAXANYPEAKS: int 80 | NISTMS_MAXBONDS: int 81 | NISTMS_MAXCIRCS: int 82 | NISTMS_MAXCONTRIBLEN: int 83 | NISTMS_MAXFORMLEN: int 84 | NISTMS_MAXMASS_PEAK: int 85 | NISTMS_MAXNAMELEN: int 86 | NISTMS_MAXPEAKS: int 87 | NISTMS_MAXREFERENCESLEN: int 88 | NISTMS_MAXREPLICATES: int 89 | NISTMS_MAXSTRINGLEN: int 90 | NISTMS_MAXSTRINGS: int 91 | NISTMS_MAXSYNONYMLEN: int 92 | NISTMS_MAX_FPOS: int 93 | NISTMS_MAX_LIBS: int 94 | NISTMS_MAX_USER_STRUCT_FILES: int 95 | NISTMS_MW_EST: int 96 | NISTMS_MW_ESTIMATION_2: int 97 | NISTMS_MW_SRCH: int 98 | NISTMS_NAMEFRAG_LEN: int 99 | NISTMS_NAME_SRCH: int 100 | NISTMS_NISTNO_SRCH: int 101 | NISTMS_NOT_A_LIBRARY: int 102 | NISTMS_NO_PRE_SRCH: int 103 | NISTMS_NUM_CONSTR_EL: int 104 | NISTMS_NUM_CONSTR_PK: int 105 | NISTMS_OPEN_MOLFILE: int 106 | NISTMS_PEPNAME_FRAG_LEN: int 107 | NISTMS_PREC_MZ_ONE: int 108 | NISTMS_RANK_PEAK: int 109 | NISTMS_REL_PEAKS: int 110 | NISTMS_REP_LIB: int 111 | NISTMS_REP_SRCH: int 112 | NISTMS_SCAN_USER_STRU_SRCH: int 113 | NISTMS_SCREEN_SRCH: int 114 | NISTMS_SEQ_ID_SRCH: int 115 | NISTMS_SET_VERSION: int 116 | NISTMS_SPEC_FLAG_PEPSEQ_GENERATED: int 117 | NISTMS_SPEC_FLAG_PEPSEQ_IN_NAME: int 118 | NISTMS_SPEC_FLAG_PEPSEQ_IN_SYN: int 119 | NISTMS_SPEC_FLAG_PEPSEQ_MASK: int 120 | NISTMS_SPEC_FLAG_PEPSEQ_NOT_PRESENT: int 121 | NISTMS_SPEC_FLAG_PEPSEQ_NOT_SEARCHED: int 122 | NISTMS_STRING_TO_ASCII: int 123 | NISTMS_STRING_TO_GREEK: int 124 | NISTMS_STRU_SRCH: int 125 | NISTMS_SUBSTR_SRCH: int 126 | NISTMS_USER_LIB: int 127 | NIST_INSTR_TYPE_NOT_IN_LIBREC: int 128 | NO_VALUE: int 129 | NUM_ADD_SPEC_MATCHFACT: int 130 | NUM_MW_ESTIMATES: int 131 | NUM_SUBS: int 132 | PRECUR_MZ_TOL_IN_PPM: int 133 | PROD_PEAK_TOL_IN_PPM: int 134 | SEARCH_MODE_CHAR_MASK: int 135 | SEARCH_MODE_FLAG_ALT_PEAK_MATCHING: int 136 | SEARCH_MODE_FLAG_FAST_PRESEARCH: int 137 | SEARCH_MODE_FLAG_GENERIC_MSMS: int 138 | SEARCH_MODE_FLAG_IGNORE_PRECURSOR: int 139 | SEARCH_MODE_FLAG_MASK: int 140 | SEARCH_MODE_FLAG_PRECUR_MZ_TOL_PPM: int 141 | SEARCH_MODE_FLAG_PROD_PEAK_TOL_PPM: int 142 | SEARCH_MODE_FLAG_REJECT_OTHER: int 143 | WARNING_NUM: int 144 | -------------------------------------------------------------------------------- /src/pyms_nist_search/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # base.py 4 | """ 5 | Base class for other PyMassSpec NIST Search classes. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2021 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | # 36 | 37 | # stdlib 38 | import json 39 | from typing import Any, Dict, Iterator, Tuple, Union 40 | 41 | # 3rd party 42 | import sdjson 43 | from chemistry_tools.cas import cas_int_to_string, check_cas_number 44 | from domdf_python_tools.doctools import prettify_docstrings 45 | from numpy import int32, int64, signedinteger 46 | from pyms.json import encode_mass_spec, encode_scan # noqa 47 | 48 | # this package 49 | from pyms_nist_search.utils import parse_name_chars 50 | 51 | __all__ = ("NISTBase", ) 52 | 53 | 54 | @prettify_docstrings 55 | class NISTBase: 56 | """ 57 | Base class for other PyMassSpec NIST Search classes. 58 | 59 | :param name: The name of the compound. 60 | :param cas: The CAS number of the compound. 61 | """ 62 | 63 | def __init__(self, name: str = '', cas: Union[str, int] = "---") -> None: 64 | 65 | self._name: str = str(name) 66 | 67 | if isinstance(cas, int): 68 | if check_cas_number(cas): 69 | cas = "---" 70 | else: 71 | cas = cas_int_to_string(cas) 72 | 73 | if cas == "0-00-0": 74 | cas = "---" 75 | self._cas: str = str(cas) 76 | 77 | @property 78 | def name(self) -> str: 79 | """ 80 | The name of the compound. 81 | """ 82 | 83 | return self._name 84 | 85 | @property 86 | def cas(self) -> str: 87 | """ 88 | The CAS number of the compound. 89 | """ 90 | 91 | return self._cas 92 | 93 | @classmethod 94 | def from_json(cls, json_data): # noqa: MAN001,MAN002 95 | """ 96 | Construct an object from json data. 97 | 98 | :param json_data: 99 | :type json_data: :class:`str` 100 | """ 101 | 102 | peak_dict = json.loads(json_data) 103 | 104 | return cls.from_dict(peak_dict) 105 | 106 | @classmethod 107 | def from_dict(cls, dictionary: Dict[str, Any]): # noqa: MAN002 108 | """ 109 | Construct an object from a dictionary. 110 | 111 | :param dictionary: 112 | """ 113 | 114 | return cls(**dictionary) 115 | 116 | def to_dict(self) -> Dict[str, Any]: 117 | """ 118 | Convert the object to a dictionary. 119 | 120 | .. versionadded:: 0.6.0 121 | """ 122 | 123 | return dict( 124 | name=self._name, 125 | cas=self.cas, 126 | ) 127 | 128 | def to_json(self) -> str: 129 | """ 130 | Convert the object to json. 131 | """ 132 | 133 | return sdjson.dumps(self.to_dict()) 134 | 135 | @classmethod 136 | def from_pynist(cls, pynist_dict: Dict[str, Any]): # noqa: MAN002 137 | """ 138 | Create an object from the raw data returned by the C extension. 139 | 140 | :param pynist_dict: 141 | """ 142 | 143 | return cls( 144 | name=parse_name_chars(pynist_dict["hit_name_chars"]), 145 | cas=cas_int_to_string(pynist_dict["cas_no"]), 146 | ) 147 | 148 | @property 149 | def __dict__(self): # noqa: MAN002 150 | return self.to_dict() 151 | 152 | def __getstate__(self) -> Dict[str, Any]: 153 | return self.to_dict() 154 | 155 | def __setstate__(self, state) -> None: # noqa: MAN001 156 | self.__init__(**state) # type: ignore[misc] 157 | 158 | def __iter__(self) -> Iterator[Tuple[str, Any]]: 159 | yield from self.to_dict().items() 160 | 161 | def __str__(self) -> str: 162 | return self.__repr__() 163 | 164 | def __eq__(self, other) -> bool: # noqa: MAN001 165 | if isinstance(other, self.__class__): 166 | return self.to_dict() == other.to_dict() 167 | 168 | return NotImplemented 169 | 170 | 171 | @sdjson.register_encoder(int64) 172 | @sdjson.register_encoder(int32) 173 | @sdjson.register_encoder(signedinteger) 174 | def serialise_numpy_int64(value: int) -> int: 175 | return int(value) 176 | -------------------------------------------------------------------------------- /src/pyms_nist_search/docker_engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # docker_engine.py 4 | """ 5 | Search engine for Linux and other platforms supporting Docker. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2021 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | # 36 | 37 | # stdlib 38 | import atexit 39 | import functools 40 | import json 41 | import pathlib 42 | import time 43 | from typing import Callable, List, Optional, Tuple 44 | 45 | # 3rd party 46 | import docker # type: ignore[import-untyped] 47 | import docker.errors # type: ignore[import-untyped] 48 | import requests 49 | import sdjson 50 | from domdf_python_tools.typing import PathLike 51 | from pyms.Spectrum import MassSpectrum 52 | 53 | # this package 54 | from pyms_nist_search.reference_data import ReferenceData 55 | from pyms_nist_search.search_result import SearchResult 56 | 57 | # this package 58 | from ._core import NISTMS_MAIN_LIB, NISTMS_REP_LIB, NISTMS_USER_LIB # type: ignore[import-not-found] 59 | 60 | __all__ = [ 61 | "require_init", 62 | "Engine", 63 | "hit_list_from_json", 64 | "hit_list_with_ref_data_from_json", 65 | ] 66 | 67 | 68 | def require_init(func: Callable) -> Callable: 69 | """ 70 | Decorator to ensure that functions do not run after the class has been uninitialised. 71 | 72 | :param func: The function or method to wrap. 73 | """ 74 | 75 | @functools.wraps(func) 76 | def wrapper(cls, *args, **kwargs) -> Callable: 77 | if not cls.initialised: 78 | raise RuntimeError( 79 | """The Search Engine has been uninitialised! 80 | Please create a new instance of the Search Engine and try again.""" 81 | ) 82 | 83 | return func(cls, *args, **kwargs) 84 | 85 | return wrapper 86 | 87 | 88 | class Engine: 89 | """ 90 | Search engine for Linux and other platforms supporting Docker. 91 | 92 | The first time the engine is initialized it will download the latest 93 | version of the docker image automatically. 94 | This can also be performed manually, such as to upgrade to the latest version, 95 | with the following command: 96 | 97 | .. prompt:: bash 98 | 99 | docker pull domdfcoding/pywine-pyms-nist 100 | 101 | The engine must be uninitialized when no longer required to shut down the underlying docker container. 102 | This is achieved with the :meth:`uninit() ` method. 103 | Alternatively, this class can be used as a contextmanager to automatically uninitialize the engine 104 | upon leaving the :keyword:`with` block: 105 | 106 | .. code-block:: python3 107 | 108 | with pyms_nist_search.Engine( 109 | FULL_PATH_TO_MAIN_LIBRARY, 110 | pyms_nist_search.NISTMS_MAIN_LIB, 111 | FULL_PATH_TO_WORK_DIR, 112 | ) as search: 113 | search.full_spectrum_search(ms, n_hits=5) 114 | 115 | .. versionchanged:: 0.6.0 Added context manager support. 116 | """ 117 | 118 | initialised: bool 119 | 120 | def __init__( 121 | self, 122 | lib_path: PathLike, 123 | lib_type: int = NISTMS_MAIN_LIB, 124 | work_dir: Optional[PathLike] = None, 125 | debug: bool = False, 126 | ): 127 | """ 128 | :param lib_path: The path to the mass spectral library. 129 | :param lib_type: The type of library. One of NISTMS_MAIN_LIB, NISTMS_USER_LIB, NISTMS_REP_LIB. 130 | :param work_dir: The path to the working directory. 131 | """ 132 | 133 | if not isinstance(lib_path, pathlib.Path): 134 | lib_path = pathlib.Path(lib_path) 135 | 136 | if not lib_path.is_dir(): 137 | raise FileNotFoundError(f"Library not found at the given path: {lib_path}") 138 | 139 | if lib_type not in {NISTMS_MAIN_LIB, NISTMS_USER_LIB, NISTMS_REP_LIB}: 140 | raise ValueError("`lib_type` must be one of NISTMS_MAIN_LIB, NISTMS_USER_LIB, NISTMS_REP_LIB.") 141 | 142 | # # Check if the server is already running 143 | # for container in client.containers.list(all=True, filters={"status": "running"}): 144 | # if container.name == "pyms-nist-server": 145 | # self.docker = container 146 | # break 147 | # else: 148 | # 149 | 150 | self.debug: bool = bool(debug) 151 | 152 | print("Launching Docker...") 153 | 154 | self._client = docker.from_env() 155 | 156 | try: 157 | self.__launch_container(lib_path, lib_type) 158 | except docker.errors.ImageNotFound: 159 | print("Docker Image not found. Downloading now.") 160 | self._client.images.pull("domdfcoding/pywine-pyms-nist") 161 | self.__launch_container(lib_path, lib_type) 162 | 163 | atexit.register(self.uninit) 164 | 165 | retry_count = 0 166 | 167 | # Wait for server to come online 168 | while retry_count < 240: 169 | try: 170 | if requests.get("http://localhost:5001/").text == "ready": 171 | self.initialised = True 172 | return 173 | 174 | except requests.exceptions.ConnectionError: 175 | time.sleep(0.5) 176 | retry_count += 1 177 | 178 | raise TimeoutError("Unable to communicate with the search server.") 179 | 180 | def __launch_container(self, lib_path: PathLike, lib_type: int) -> None: 181 | self.docker = self._client.containers.run( 182 | "domdfcoding/pywine-pyms-nist", 183 | ports={5001: 5001}, 184 | detach=True, 185 | name="pyms-nist-server", 186 | # remove=True, 187 | # stdout=False, 188 | # stderr=False, 189 | stdin_open=False, 190 | volumes={lib_path: {"bind": "/mainlib", "mode": "ro"}}, 191 | environment=[f"LIBTYPE={lib_type}"], 192 | ) 193 | 194 | def __enter__(self) -> "Engine": 195 | return self 196 | 197 | def __exit__(self, exc_type, exc_val, exc_tb): 198 | self.uninit() 199 | 200 | def uninit(self) -> None: 201 | """ 202 | Uninitialize the Search Engine. 203 | """ 204 | 205 | if self.initialised: 206 | 207 | print("Shutting down docker server") 208 | 209 | if self.debug: 210 | print("Server log follows:") 211 | print(self.docker.logs(timestamps=True).decode("utf-8")) 212 | 213 | try: 214 | self.docker.stop() 215 | self.docker.remove() 216 | except docker.errors.NotFound: 217 | print("Unable to shut down the docker server") 218 | 219 | self.initialised = False 220 | 221 | @require_init 222 | def spectrum_search(self, mass_spec: MassSpectrum, n_hits: int = 5) -> List[SearchResult]: 223 | """ 224 | Perform a Quick Spectrum Search of the mass spectral library. 225 | 226 | :param mass_spec: The mass spectrum to search against the library. 227 | :param n_hits: The number of hits to return. 228 | 229 | :return: List of possible identities for the mass spectrum. 230 | """ 231 | 232 | if not isinstance(mass_spec, MassSpectrum): 233 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 234 | 235 | retry_count = 0 236 | 237 | # Keep trying until it works 238 | while retry_count < 240: 239 | try: 240 | res = requests.post( 241 | f"http://localhost:5001/search/quick/?n_hits={n_hits}", 242 | json=sdjson.dumps(mass_spec), 243 | ) 244 | print(res.text) 245 | return hit_list_from_json(res.text) 246 | 247 | except requests.exceptions.ConnectionError: 248 | time.sleep(0.5) 249 | retry_count += 1 250 | 251 | raise TimeoutError("Unable to communicate with the search server.") 252 | 253 | @staticmethod 254 | def cas_search(cas: str) -> List[SearchResult]: 255 | """ 256 | Search for a compound by CAS number. 257 | 258 | :param cas: 259 | 260 | :return: List of results for CAS number (usually just one result). 261 | """ 262 | 263 | retry_count = 0 264 | 265 | # Keep trying until it works 266 | while retry_count < 240: 267 | try: 268 | res = requests.post(f"http://localhost:5001/search/cas/{cas}") 269 | res.raise_for_status() 270 | return hit_list_from_json(res.text) 271 | 272 | except requests.exceptions.ConnectionError: 273 | time.sleep(0.5) 274 | retry_count += 1 275 | 276 | raise TimeoutError("Unable to communicate with the search server.") 277 | 278 | @require_init 279 | def full_spectrum_search( 280 | self, 281 | mass_spec: MassSpectrum, 282 | n_hits: int = 5, 283 | ) -> List[SearchResult]: 284 | """ 285 | Perform a Full Spectrum Search of the mass spectral library. 286 | 287 | :param mass_spec: The mass spectrum to search against the library. 288 | :param n_hits: The number of hits to return. 289 | 290 | :return: List of possible identities for the mass spectrum. 291 | """ 292 | 293 | if not isinstance(mass_spec, MassSpectrum): 294 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 295 | 296 | retry_count = 0 297 | 298 | # Keep trying until it works 299 | while retry_count < 240: 300 | try: 301 | res = requests.post( 302 | f"http://localhost:5001/search/spectrum/?n_hits={n_hits}", json=sdjson.dumps(mass_spec) 303 | ) 304 | return hit_list_from_json(res.text) 305 | 306 | except requests.exceptions.ConnectionError: 307 | time.sleep(0.5) 308 | retry_count += 1 309 | 310 | raise TimeoutError("Unable to communicate with the search server.") 311 | 312 | @require_init 313 | def full_search_with_ref_data( 314 | self, 315 | mass_spec: MassSpectrum, 316 | n_hits: int = 5, 317 | ) -> List[Tuple[SearchResult, ReferenceData]]: 318 | """ 319 | Perform a Full Spectrum Search of the mass spectral library, including reference data. 320 | 321 | :param mass_spec: The mass spectrum to search against the library. 322 | :param n_hits: The number of hits to return. 323 | 324 | :return: List of tuples containing possible identities 325 | for the mass spectrum, and the reference data. 326 | """ 327 | 328 | if not isinstance(mass_spec, MassSpectrum): 329 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 330 | 331 | retry_count = 0 332 | 333 | # Keep trying until it works 334 | while retry_count < 240: 335 | try: 336 | res = requests.post( 337 | f"http://localhost:5001/search/spectrum_with_ref_data/?n_hits={n_hits}", 338 | json=sdjson.dumps(mass_spec) 339 | ) 340 | return hit_list_with_ref_data_from_json(res.text) 341 | except requests.exceptions.ConnectionError: 342 | time.sleep(0.5) 343 | retry_count += 1 344 | 345 | raise TimeoutError("Unable to communicate with the search server.") 346 | 347 | @require_init 348 | def get_reference_data(self, spec_loc: int) -> ReferenceData: 349 | """ 350 | Get reference data from the library for the compound at the given location. 351 | 352 | :param spec_loc: 353 | """ 354 | 355 | retry_count = 0 356 | 357 | # Keep trying until it works 358 | while retry_count < 240: 359 | try: 360 | res = requests.post(f"http://localhost:5001/search/loc/{spec_loc}") 361 | return ReferenceData(**json.loads(res.text)) 362 | 363 | except requests.exceptions.ConnectionError: 364 | time.sleep(0.5) 365 | 366 | raise TimeoutError("Unable to communicate with the search server.") 367 | 368 | 369 | def hit_list_from_json(json_data: str) -> List[SearchResult]: 370 | """ 371 | Parse json data into a list of SearchResult objects. 372 | 373 | :param json_data: str 374 | """ 375 | 376 | raw_output = json.loads(json_data) 377 | 378 | hit_list = [] 379 | 380 | for hit in raw_output: 381 | hit_list.append(SearchResult(**hit)) 382 | 383 | return hit_list 384 | 385 | 386 | def hit_list_with_ref_data_from_json(json_data: str) -> List[Tuple[SearchResult, ReferenceData]]: 387 | """ 388 | Parse json data into a list of (SearchResult, ReferenceData) tuples. 389 | 390 | :param json_data: str 391 | """ 392 | 393 | raw_output = json.loads(json_data) 394 | 395 | hit_list = [] 396 | 397 | for hit, ref_data in raw_output: 398 | hit_list.append((SearchResult(**hit), ReferenceData(**ref_data))) 399 | 400 | return hit_list 401 | -------------------------------------------------------------------------------- /src/pyms_nist_search/mona_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # mona_tools.py 4 | """ 5 | Functions for working with MoNA JSON data. 6 | 7 | It could probably be its own package. 8 | """ 9 | # 10 | # This file is part of PyMassSpec NIST Search 11 | # Python interface to the NIST MS Search DLL 12 | # 13 | # Copyright (c) 2020 Dominic Davis-Foster 14 | # 15 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 16 | # it under the terms of the GNU Lesser General Public License as 17 | # published by the Free Software Foundation; either version 3 of 18 | # the License, or (at your option) any later version. 19 | # 20 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU Lesser General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU Lesser General Public 26 | # License along with this program; if not, write to the Free Software 27 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 28 | # MA 02110-1301, USA. 29 | # 30 | 31 | # stdlib 32 | from typing import Any, Dict, Iterable, List, Mapping, Set 33 | 34 | # 3rd party 35 | from pyms.Spectrum import MassSpectrum 36 | 37 | __all__ = ( 38 | "prep_match_list", 39 | "other_dbs", 40 | "mona_skip_compound_props", 41 | "mona_skip_categories", 42 | "mona_skip_properties", 43 | "parse_metadata", 44 | "mass_spec_from_mona", 45 | ) 46 | 47 | 48 | def prep_match_list(match_list: Iterable[str]) -> Set[str]: 49 | """ 50 | Prepare a list of matches for caseless matching with :meth:`str.casefold`. 51 | 52 | :param match_list: 53 | """ 54 | 55 | return {x.casefold() for x in match_list} 56 | 57 | 58 | other_dbs: Set[str] = prep_match_list([ 59 | "SMILES", 60 | "InChI", 61 | "InChIKey", 62 | "kegg", 63 | "chebi", 64 | "pubchem cid", 65 | "chemspider", 66 | "PubChem", 67 | "lipidbank", 68 | "knapsack", 69 | "lipidmaps", 70 | "hmdb", 71 | ]) 72 | 73 | mona_skip_compound_props: Set[str] = prep_match_list([*other_dbs, "compound class"]) 74 | 75 | mona_skip_categories: Set[str] = prep_match_list([ 76 | "mass spectrometry", 77 | "focused ion", 78 | "spectral properties", 79 | "acquisition properties", 80 | ]) 81 | 82 | mona_skip_properties: Set[str] = prep_match_list([ 83 | "date", 84 | "ionization mode", 85 | "instrument", 86 | "instrument type", 87 | "chromatography type", 88 | "column", 89 | "guard column", 90 | "mobile phase", 91 | "column temperature", 92 | "flow rate", 93 | "injection volume", 94 | "injection", 95 | "injection temperature", 96 | "data transformation", 97 | "chromatogram", 98 | "retention index", 99 | "retention index type", 100 | "detector voltage", 101 | "derivatization type", 102 | "derivatization formula", 103 | "derivatization mass", 104 | "scan range", 105 | "ms level", 106 | "comment", 107 | "oven temperature", 108 | "retention time", 109 | "whole", 110 | "copyright", 111 | "transfer line temperature", 112 | "publication", 113 | "ion source temperature", 114 | "ionization energy", 115 | "scannumber", 116 | "quantmass", 117 | "intensity", 118 | "origin", 119 | "modelion", 120 | "modelionheight", 121 | "modelionarea", 122 | "integratedheight", 123 | "integratedarea", 124 | "comments", 125 | "sample file", 126 | "sample type", 127 | "species", 128 | "organ", 129 | "annotation", 130 | "biological matrix used sample preparation", 131 | "collision energy", 132 | "precursor type", 133 | "carrier gas", 134 | "recalibrate", 135 | "column temperature gradient", 136 | "transferline temperature", 137 | "sample introduction", 138 | "derivatization", 139 | "ionization", 140 | ]) 141 | 142 | 143 | def parse_metadata(mona_data: Dict[str, Any]) -> Dict[str, Any]: 144 | """ 145 | Parse metadata for the compound. 146 | 147 | :param mona_data: 148 | """ 149 | 150 | compound: Dict = mona_data["compound"][0] 151 | compound_metadata: List = compound["metaData"] 152 | metadata: List = mona_data["metaData"] 153 | submitter: Dict = mona_data["submitter"] 154 | 155 | properties_dict: Dict[str, Any] = { 156 | "formula": '', 157 | "mw": 0, 158 | "exact_mass": 0.0, 159 | "cas": '', 160 | "contributor": '', 161 | "license": '', 162 | } 163 | 164 | if "id" in mona_data: 165 | properties_dict["id"] = mona_data["id"] 166 | else: 167 | properties_dict["id"] = '' 168 | 169 | prop_lookup: Dict[str, str] = { 170 | "accession": "id", 171 | "exact mass": "exact_mass", 172 | "author": "contributor", 173 | "molecular formula": "formula", 174 | "total exact mass": "exact_mass", 175 | "accurate mass": "exact_mass", 176 | "cas number": "cas", 177 | "cas": "cas", 178 | "license": "license", 179 | } 180 | 181 | def set_prop_value(property_name: str, prop_: Mapping[str, Any]) -> None: 182 | if not properties_dict[property_name]: 183 | properties_dict[property_name] = prop_["value"] 184 | 185 | def parse_compound_prop(prop_: Mapping[str, Any]) -> None: 186 | # Put props in order of priority 187 | for prop_name in ["molecular formula", "total exact mass", "cas number", "cas"]: 188 | if prop_["name"].casefold() == prop_name: 189 | set_prop_value(prop_lookup[prop_name], prop_) 190 | break 191 | else: 192 | print(prop_) 193 | 194 | def parse_property(prop_: Mapping[str, Any]) -> None: 195 | # Put props in order of priority 196 | for prop_name in ["accession", "exact mass", "author", "institution", "accurate mass", "license"]: 197 | if prop_["name"].casefold() == prop_name: 198 | set_prop_value(prop_lookup[prop_name], prop_) 199 | break 200 | else: 201 | print(prop_) 202 | 203 | for prop in compound_metadata: 204 | if prop["name"].casefold() in mona_skip_compound_props: 205 | continue 206 | 207 | elif not prop["computed"]: 208 | parse_compound_prop(prop) 209 | # prioritise not computed 210 | elif prop["computed"]: 211 | parse_compound_prop(prop) 212 | else: 213 | # Unknown property 214 | print(prop) 215 | 216 | for prop in metadata: 217 | if "category" in prop and prop["category"].casefold() in mona_skip_categories: 218 | continue 219 | 220 | elif prop["name"].casefold() in prep_match_list(( 221 | *mona_skip_properties, 222 | "data format", 223 | "institution", 224 | "formula", 225 | "ion type", 226 | )): 227 | # todo: "data format" 228 | continue 229 | 230 | elif not prop["computed"]: 231 | # prioritise not computed 232 | parse_property(prop) 233 | elif prop["computed"]: 234 | parse_property(prop) 235 | else: 236 | # Unknown property 237 | print(prop) 238 | 239 | if not properties_dict["contributor"] and "institution" in submitter: 240 | properties_dict["contributor"] = submitter["institution"] 241 | 242 | if not properties_dict["mw"]: 243 | properties_dict["mw"] = properties_dict["exact_mass"] 244 | 245 | # ignored following keys 246 | # dateCreated 247 | # lastUpdated 248 | # lastCurated 249 | # score 250 | # splash 251 | # tags 252 | # library 253 | 254 | return properties_dict 255 | 256 | 257 | def mass_spec_from_mona(mona_ms_string: str) -> MassSpectrum: 258 | """ 259 | Create a :class:`pyms.Spectrum.MassSpectrum` object from the MoNA JSON representation of the spectrum. 260 | 261 | :param mona_ms_string: 262 | """ 263 | 264 | pairs = [val.split(':') for val in mona_ms_string.split(' ')] 265 | return MassSpectrum.from_mz_int_pairs([(float(mz), float(int_)) for mz, int_ in pairs]) 266 | -------------------------------------------------------------------------------- /src/pyms_nist_search/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/py.typed -------------------------------------------------------------------------------- /src/pyms_nist_search/reference_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # reference_data.py 4 | """ 5 | Class to store reference data from NIST MS Search. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2021 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | 36 | # stdlib 37 | import copy 38 | import json 39 | import warnings 40 | from typing import Any, Dict, List, Optional, Sequence, Type, Union 41 | 42 | # 3rd party 43 | import sdjson 44 | from domdf_python_tools.doctools import prettify_docstrings 45 | from domdf_python_tools.iterative import chunks 46 | from domdf_python_tools.paths import PathPlus 47 | from domdf_python_tools.typing import PathLike 48 | from pyms.Spectrum import MassSpectrum, normalize_mass_spec 49 | from pyms.Utils.jcamp import JcampTagWarning, header_info_fields, xydata_tags 50 | from pyms.Utils.Math import is_float 51 | 52 | # this package 53 | from pyms_nist_search.base import NISTBase 54 | from pyms_nist_search.mona_tools import mass_spec_from_mona, parse_metadata 55 | from pyms_nist_search.templates import * 56 | from pyms_nist_search.utils import parse_name_chars 57 | 58 | __all__ = ("ReferenceData", ) 59 | 60 | 61 | @prettify_docstrings 62 | class ReferenceData(NISTBase): 63 | """ 64 | Class to store reference data from NIST MS Search. 65 | 66 | :param name: The name of the compound. 67 | :param cas: The CAS number of the compound. 68 | :param nist_no: 69 | :param id: 70 | :param mw: 71 | :param formula: The formula of the compound. 72 | :param contributor: The contributor to the library. 73 | :param mass_spec: The reference mass spectrum. 74 | :param synonyms: List of synonyms for the compound. 75 | 76 | .. latex:vspace:: 60px 77 | """ 78 | 79 | _exact_mass: float 80 | _mass_spec: Optional[MassSpectrum] 81 | _synonyms: List[str] 82 | 83 | def __init__( 84 | self, 85 | name: str = '', 86 | cas: Union[str, int] = "---", 87 | nist_no: Union[int, str] = 0, 88 | id: Union[str, int] = '', # noqa: A002 # pylint: disable=redefined-builtin 89 | mw: Union[float, str] = 0.0, 90 | formula: str = '', 91 | contributor: str = '', 92 | mass_spec: Optional[MassSpectrum] = None, 93 | synonyms: Optional[Sequence[str]] = None, 94 | exact_mass: Optional[Any] = None, 95 | ) -> None: 96 | 97 | NISTBase.__init__(self, name, cas) 98 | 99 | self._formula: str = str(formula) 100 | self._contributor: str = str(contributor) 101 | 102 | self._nist_no: int = int(nist_no) 103 | self._id: str = str(id) 104 | 105 | self._mw: int = int(mw) 106 | 107 | if not exact_mass: 108 | self._exact_mass = float(mw) 109 | else: 110 | self._exact_mass = float(exact_mass) 111 | 112 | if mass_spec is None: 113 | self._mass_spec = None 114 | elif isinstance(mass_spec, dict): 115 | self._mass_spec = MassSpectrum(**mass_spec) 116 | else: 117 | self._mass_spec = copy.copy(mass_spec) 118 | 119 | if synonyms is None: 120 | self._synonyms = [] 121 | else: 122 | self._synonyms = [str(synonym) for synonym in synonyms] 123 | 124 | @property 125 | def formula(self) -> str: 126 | """ 127 | The formula of the compound. 128 | """ 129 | 130 | return self._formula 131 | 132 | @property 133 | def contributor(self) -> str: 134 | """ 135 | The name of the contributor to the library. 136 | """ 137 | 138 | return self._contributor 139 | 140 | @property 141 | def nist_no(self) -> int: 142 | """ 143 | The NIST number of the compund. 144 | """ 145 | 146 | return self._nist_no 147 | 148 | @property 149 | def id(self) -> str: 150 | """ 151 | The ID of the compound. 152 | """ 153 | 154 | return self._id 155 | 156 | @property 157 | def mw(self) -> int: 158 | """ 159 | The molecular weight of the compound. 160 | """ 161 | 162 | return self._mw 163 | 164 | @property 165 | def exact_mass(self) -> float: 166 | """ 167 | The exact mass of the compound. 168 | """ 169 | 170 | return self._exact_mass 171 | 172 | @property 173 | def mass_spec(self) -> Optional[MassSpectrum]: 174 | """ 175 | The mass spectrum of the compound. 176 | """ 177 | 178 | return copy.copy(self._mass_spec) 179 | 180 | @property 181 | def synonyms(self) -> List[str]: 182 | """ 183 | A list of synonyms for the compound. 184 | """ 185 | 186 | return self._synonyms[:] 187 | 188 | @classmethod 189 | def from_pynist(cls, pynist_dict: Dict[str, Any]) -> "ReferenceData": 190 | """ 191 | Create a :class:`ReferenceData` object from the raw data returned by the C extension. 192 | 193 | :param pynist_dict: 194 | """ 195 | 196 | return cls( 197 | name=parse_name_chars(pynist_dict["name_chars"]), 198 | cas=pynist_dict["cas"], 199 | formula=pynist_dict["formula"], 200 | contributor=pynist_dict["contributor"], 201 | nist_no=pynist_dict["nist_no"], 202 | id=pynist_dict["id"], 203 | mw=pynist_dict["mw"], 204 | mass_spec=MassSpectrum(pynist_dict["mass_list"], pynist_dict["intensity_list"]), 205 | synonyms=[parse_name_chars(synonym) for synonym in pynist_dict["synonyms_chars"]], 206 | ) 207 | 208 | def __repr__(self) -> str: 209 | return f"Reference Data: {self.name} \t({self.cas})" 210 | 211 | def to_dict(self) -> Dict[str, Any]: 212 | """ 213 | Convert the object to a dictionary. 214 | 215 | .. versionadded:: 0.6.0 216 | """ 217 | 218 | return dict( 219 | name=self.name, 220 | cas=self.cas, 221 | formula=self.formula, 222 | contributor=self.contributor, 223 | nist_no=self.nist_no, 224 | id=self.id, 225 | mw=self.mw, 226 | exact_mass=self.exact_mass, 227 | synonyms=self.synonyms[:], 228 | mass_spec=self.mass_spec, 229 | ) 230 | 231 | @property 232 | def __dict__(self): # noqa: MAN002 233 | return self.to_dict() 234 | 235 | @classmethod 236 | def from_jcamp(cls, file_name: PathLike, ignore_warnings: bool = True) -> "ReferenceData": 237 | """ 238 | Create a ReferenceData object from a JCAMP-DX file. 239 | 240 | :param file_name: Path of the file to read. 241 | :param ignore_warnings: Whether warnings about invalid tags should be shown. 242 | 243 | :authors: Qiao Wang, Andrew Isaac, Vladimir Likic, David Kainer, Dominic Davis-Foster 244 | """ 245 | 246 | with warnings.catch_warnings(): 247 | 248 | if ignore_warnings: 249 | warnings.simplefilter("ignore", JcampTagWarning) 250 | 251 | file_name = PathPlus(file_name) 252 | 253 | # Commented this line because it also gets printed when the MassSpectrum is created 254 | # print(f" -> Reading JCAMP file '{file_name}'") 255 | lines_list = file_name.read_lines() 256 | last_tag = None 257 | 258 | header_info: Dict[str, Any] = {} # Dictionary containing header information 259 | 260 | for line in lines_list: 261 | 262 | if len(line.strip()): 263 | if line.startswith("##"): 264 | # key word or information 265 | fields = line.split('=', 1) 266 | current_tag = fields[0] = fields[0].lstrip("##").upper() 267 | last_tag = fields[0] 268 | fields[1] = fields[1].strip() 269 | 270 | if current_tag.upper().startswith("END"): 271 | break 272 | 273 | elif current_tag in xydata_tags: 274 | continue 275 | 276 | elif current_tag in header_info_fields: 277 | if fields[1].isdigit(): 278 | header_info[current_tag] = int(fields[1]) 279 | elif is_float(fields[1]): 280 | header_info[current_tag] = float(fields[1]) 281 | else: 282 | header_info[current_tag] = fields[1] 283 | else: 284 | warnings.warn(current_tag, JcampTagWarning) 285 | 286 | else: 287 | if last_tag in header_info: 288 | header_info[last_tag] += f"{line}" 289 | 290 | return cls( 291 | name=header_info["TITLE"], 292 | cas=header_info["CAS REGISTRY NO"], 293 | nist_no=header_info["$NIST MASS SPEC NO"], 294 | contributor=header_info["ORIGIN"], 295 | formula=header_info["MOLFORM"], 296 | mw=header_info["MW"], 297 | mass_spec=MassSpectrum.from_jcamp(file_name), 298 | ) 299 | 300 | def to_json(self) -> str: 301 | """ 302 | Convert the object to JSON. 303 | """ 304 | 305 | return sdjson.dumps(self.to_dict()) 306 | 307 | @classmethod 308 | def from_json(cls: Type["ReferenceData"], json_data: str) -> "ReferenceData": 309 | """ 310 | Construct an object from JSON data. 311 | 312 | :param json_data: 313 | """ 314 | 315 | peak_dict = json.loads(json_data) 316 | # peak_dict["mass_spec"] = MassSpectrum.from_dict(peak_dict["mass_spec"]) 317 | return cls.from_dict(peak_dict) 318 | 319 | @classmethod 320 | def from_mona_dict(cls, mona_data: Dict) -> "ReferenceData": 321 | """ 322 | Construct an object from Massbank of North America json data 323 | that has been loaded into a dictionary. 324 | 325 | :param mona_data: dict 326 | """ # noqa: D400 327 | 328 | compound: Dict = mona_data["compound"][0] 329 | names: List = compound["names"] 330 | name: str = names[0]["name"] 331 | synonyms: List = [name for name in names[1:]] 332 | 333 | properties_dict = parse_metadata(mona_data) 334 | 335 | # Remove unwanted properties 336 | del properties_dict["license"] 337 | 338 | mass_spec = mass_spec_from_mona(mona_data["spectrum"]) 339 | 340 | return cls( 341 | name=name, 342 | mass_spec=mass_spec, 343 | synonyms=synonyms, 344 | **properties_dict, 345 | ) 346 | 347 | def to_msp(self) -> str: 348 | """ 349 | Returns the ReferenceData object as an MSP file similar to that produced by 350 | NIST MS Search's export function. 351 | """ # noqa: D400 352 | 353 | if not self.mass_spec: 354 | raise ValueError("No mass spectrum included in the reference data.") 355 | 356 | normalized_ms = normalize_mass_spec(self.mass_spec, max_intensity=999) 357 | num_peaks = len(self.mass_spec) 358 | 359 | mz_int_pairs = [f"{mz} {intensity}" for mz, intensity in normalized_ms.iter_peaks()] 360 | 361 | spec_block = [] 362 | 363 | for row in list(chunks(mz_int_pairs, 5)): 364 | spec_block.append("; ".join(x for x in row)) 365 | 366 | msp_text = msp_template.render( 367 | ref_data=self, 368 | num_peaks=num_peaks, 369 | spec_block='\n'.join(spec_block), 370 | ) 371 | 372 | return msp_text 373 | 374 | 375 | @sdjson.register_encoder(ReferenceData) 376 | def encode_reference_data(obj: ReferenceData) -> Dict[str, Any]: 377 | return obj.to_dict() 378 | -------------------------------------------------------------------------------- /src/pyms_nist_search/search_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # search_result.py 4 | """ 5 | Class to store search results from NIST MS Search. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2021 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | 36 | # stdlib 37 | from typing import Any, Dict, Union 38 | 39 | # 3rd party 40 | from domdf_python_tools.doctools import prettify_docstrings 41 | from sdjson import register_encoder 42 | 43 | # this package 44 | from pyms_nist_search.base import NISTBase 45 | from pyms_nist_search.utils import parse_name_chars 46 | 47 | __all__ = ["SearchResult"] 48 | 49 | 50 | @prettify_docstrings 51 | class SearchResult(NISTBase): 52 | """ 53 | Class to store search results from NIST MS Search. 54 | 55 | :param name: The name of the compound 56 | :param cas: The CAS number of the compound 57 | :param match_factor: 58 | :param reverse_match_factor: 59 | :param hit_prob: 60 | :param spec_loc: The location of the reference spectrum in the library. 61 | 62 | .. latex:vspace:: 20px 63 | """ 64 | 65 | def __init__( 66 | self, 67 | name: str = '', 68 | cas: Union[str, int] = "---", 69 | match_factor: float = 0, 70 | reverse_match_factor: float = 0, 71 | hit_prob: float = 0.0, 72 | spec_loc: float = 0, 73 | ) -> None: 74 | NISTBase.__init__(self, name, cas) 75 | 76 | self._match_factor: int = int(match_factor) 77 | self._reverse_match_factor: int = int(reverse_match_factor) 78 | 79 | self._hit_prob: float = float(hit_prob) 80 | self._spec_loc: int = int(spec_loc) 81 | 82 | @property 83 | def match_factor(self) -> int: 84 | """ 85 | Returns a score (out of 1000) representing the similarity of the searched 86 | mass spectrum to the search result. 87 | """ # noqa: D400 88 | 89 | return int(self._match_factor) 90 | 91 | @property 92 | def reverse_match_factor(self) -> int: 93 | """ 94 | A score (out of 1000) representing the similarity of the searched 95 | mass spectrum to the search result, but ignoring any peaks that are in 96 | the searched mass spectrum but not in the library spectrum. 97 | """ # noqa: D400 98 | 99 | return int(self._reverse_match_factor) 100 | 101 | @property 102 | def hit_prob(self) -> float: 103 | """ 104 | Returns the probability of the hit being the compound responsible for the mass spectrum. 105 | """ 106 | 107 | return float(self._hit_prob) 108 | 109 | @property 110 | def spec_loc(self) -> int: 111 | """ 112 | The location of the reference spectrum in the library. 113 | 114 | This can then be searched using the :meth:`~pynist.win_engine.Engine.get_reference_data` method of the 115 | search engine to obtain the reference data. 116 | """ 117 | 118 | return int(self._spec_loc) 119 | 120 | @classmethod 121 | def from_pynist(cls, pynist_dict: Dict[str, Any]) -> "SearchResult": 122 | """ 123 | Create a :class:`~.SearchResult` object from the raw data returned by the C extension. 124 | 125 | :param pynist_dict: 126 | """ 127 | 128 | return cls( 129 | name=parse_name_chars(pynist_dict["hit_name_chars"]), 130 | cas=pynist_dict["cas_no"], 131 | match_factor=pynist_dict["sim_num"], 132 | reverse_match_factor=pynist_dict["rev_sim_num"], 133 | hit_prob=pynist_dict["hit_prob"] / 100, 134 | spec_loc=pynist_dict["spec_loc"], 135 | ) 136 | 137 | def __repr__(self) -> str: 138 | return f"Search Result: {self.name} \t({self.match_factor})" 139 | 140 | def to_dict(self) -> Dict[str, Any]: 141 | """ 142 | Convert the object to a dictionary. 143 | 144 | .. versionadded:: 0.6.0 145 | """ 146 | 147 | return dict( 148 | name=self._name, 149 | cas=self.cas, 150 | match_factor=self.match_factor, 151 | reverse_match_factor=self.reverse_match_factor, 152 | spec_loc=self.spec_loc, 153 | hit_prob=self.hit_prob, 154 | ) 155 | 156 | @property 157 | def __dict__(self): # noqa: MAN002 158 | return self.to_dict() 159 | 160 | 161 | @register_encoder(SearchResult) 162 | def encode_search_result(obj: SearchResult) -> Dict[str, Any]: 163 | return obj.to_dict() 164 | -------------------------------------------------------------------------------- /src/pyms_nist_search/templates/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | """ 5 | Templates for exporting files. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | 29 | # stdlib 30 | import pathlib 31 | 32 | # 3rd party 33 | from jinja2 import Template 34 | 35 | __all__ = ("msp_template", ) 36 | 37 | msp_template_file = pathlib.Path(__file__).parent / "msp_template" 38 | msp_template = Template(msp_template_file.read_text()) 39 | del msp_template_file 40 | -------------------------------------------------------------------------------- /src/pyms_nist_search/templates/msp_template: -------------------------------------------------------------------------------- 1 | Name: {{ ref_data.name }} 2 | {% for synonym in ref_data.synonyms %}Synon: {{ synonym }}{{ "\n" }}{% endfor %}Formula: {{ ref_data.formula }} 3 | MW: {{ ref_data.mw }} 4 | CAS#: {{ ref_data.cas }} 5 | NIST#: {{ ref_data.nist_no }} 6 | DB#: {{ ref_data.id }} 7 | Comments: {{ ref_data.contributor }} 8 | Num Peaks: {{ num_peaks }} 9 | {{ spec_block }} 10 | -------------------------------------------------------------------------------- /src/pyms_nist_search/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # utils.py 4 | """ 5 | General utilities. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | # 36 | 37 | # stdlib 38 | import warnings 39 | from typing import Sequence 40 | 41 | # 3rd party 42 | from pyms.Spectrum import MassSpectrum 43 | 44 | __all__ = ["pack", "parse_name_chars"] 45 | 46 | 47 | def pack(mass_spec: MassSpectrum, top: int = 20) -> str: 48 | """ 49 | Convert a pyms.Spectrum.MassSpectrum object into a string. 50 | 51 | Adapted from https://sourceforge.net/projects/mzapi-live/ 52 | 53 | :param mass_spec: 54 | :param top: The number of largest peaks to identify 55 | """ 56 | 57 | values = list(zip(mass_spec.mass_list, mass_spec.intensity_list)) 58 | 59 | values.sort(key=lambda s: s[1], reverse=True) 60 | norm = values[0][1] 61 | 62 | spectrum = [(a, 999.0 * b / norm) for (a, b) in values[:top]] 63 | spectrum.sort() 64 | 65 | return '*'.join([f"{a:.2f}\t{b:.2f}" for (a, b) in spectrum]) + '*' 66 | 67 | 68 | def parse_name_chars(name_char_list: Sequence[int]) -> str: 69 | """ 70 | Takes a list of Unicode character codes and converts them to characters, 71 | taking into account the special codes used by the NIST DLL. 72 | 73 | :param name_char_list: 74 | 75 | :return: The parsed name. 76 | """ # noqa: D400 77 | 78 | hit_name = '' 79 | errors = [] # Buffer the errors to display at the end 80 | 81 | # TODO: can we do away with the -1? 82 | for dec in name_char_list[:-1]: 83 | if dec == 0: 84 | break 85 | 86 | if dec == 224: 87 | char = 'α' 88 | elif dec == 225: 89 | char = 'β' 90 | elif dec == 231: 91 | char = 'γ' 92 | elif dec == 235: 93 | char = 'δ' 94 | elif dec == 238: 95 | char = 'ε' 96 | elif dec == 227: 97 | char = 'π' 98 | elif dec == 229: 99 | char = 'σ' 100 | elif dec == 230: 101 | char = 'μ' 102 | elif dec == 234: 103 | char = 'ω' 104 | elif dec == 241: 105 | char = '±' 106 | elif dec == 252: 107 | char = 'η' 108 | else: 109 | try: 110 | char = chr(dec) 111 | except ValueError: 112 | errors.append(dec) 113 | # print(f"Unable to parse character with code {dec}") 114 | char = '�' 115 | 116 | # List of problem codes encountered so far: 117 | # -26, which should be a μ (03BC) 118 | 119 | if char != '\x00': 120 | hit_name += char 121 | 122 | if errors: 123 | warnings.warn(f"Unable to parse the following character codes for string {hit_name}: {errors}.") 124 | 125 | return hit_name 126 | -------------------------------------------------------------------------------- /src/pyms_nist_search/win_engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # pynist_search_server.py 4 | """ 5 | Search engine for Windows systems. 6 | """ 7 | # 8 | # This file is part of PyMassSpec NIST Search 9 | # Python interface to the NIST MS Search DLL 10 | # 11 | # Copyright (c) 2020-2021 Dominic Davis-Foster 12 | # 13 | # PyMassSpec NIST Search is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU Lesser General Public License as 15 | # published by the Free Software Foundation; either version 3 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # PyMassSpec NIST Search is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU Lesser General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU Lesser General Public 24 | # License along with this program; if not, write to the Free Software 25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 26 | # MA 02110-1301, USA. 27 | # 28 | # PyMassSpec NIST Search includes the redistributable binaries for NIST MS Search in 29 | # the x86 and x64 directories. Available from 30 | # ftp://chemdata.nist.gov/mass-spc/v1_7/NISTDLL3.zip . 31 | # ctnt66.dll and ctnt66_64.dll copyright 1984-1996 FairCom Corporation. 32 | # "FairCom" and "c-tree Plus" are trademarks of FairCom Corporation 33 | # and are registered in the United States and other countries. 34 | # All Rights Reserved. 35 | 36 | # stdlib 37 | import atexit 38 | import pathlib 39 | from typing import List, Optional, Tuple 40 | 41 | # 3rd party 42 | from domdf_python_tools.typing import PathLike 43 | from pyms.Spectrum import MassSpectrum 44 | 45 | # this package 46 | from pyms_nist_search.reference_data import ReferenceData 47 | from pyms_nist_search.search_result import SearchResult 48 | from pyms_nist_search.utils import pack 49 | 50 | # this package 51 | from . import _core 52 | 53 | __all__ = ("Engine", ) 54 | 55 | 56 | class Engine: 57 | """ 58 | Search engine for Windows systems. 59 | 60 | .. TODO:: Search by Name. See page 13 of the documentation. 61 | 62 | :param lib_path: The path to the mass spectral library. 63 | :param lib_type: The type of library. One of ``NISTMS_MAIN_LIB``, ``NISTMS_USER_LIB``, ``NISTMS_REP_LIB``. 64 | :param work_dir: The path to the working directory. 65 | 66 | .. latex:vspace:: 50px 67 | """ 68 | 69 | def __init__( 70 | self, 71 | lib_path: PathLike, 72 | lib_type: int = _core.NISTMS_MAIN_LIB, 73 | work_dir: Optional[PathLike] = None, 74 | debug: bool = False, 75 | ): 76 | if not isinstance(lib_path, pathlib.Path): 77 | lib_path = pathlib.Path(lib_path) 78 | 79 | if work_dir is None: 80 | work_dir = pathlib.Path.cwd() 81 | elif not isinstance(work_dir, pathlib.Path): 82 | work_dir = pathlib.Path(work_dir) 83 | 84 | # Create work_dir if it doesn't exist 85 | if not work_dir.is_dir(): 86 | work_dir.mkdir() 87 | 88 | if not lib_path.is_dir(): 89 | raise FileNotFoundError(f"Library not found at the given path: {lib_path}") 90 | 91 | if lib_type not in {_core.NISTMS_MAIN_LIB, _core.NISTMS_USER_LIB, _core.NISTMS_REP_LIB}: 92 | raise ValueError("`lib_type` must be one of NISTMS_MAIN_LIB, NISTMS_USER_LIB, NISTMS_REP_LIB.") 93 | 94 | self.debug = debug 95 | 96 | _core._init_api(str(lib_path), lib_type, str(work_dir)) 97 | 98 | atexit.register(self.uninit) 99 | 100 | def uninit(self) -> None: 101 | """ 102 | Uninitialize the Search Engine. 103 | """ 104 | 105 | @staticmethod 106 | def spectrum_search(mass_spec: MassSpectrum, n_hits: int = 5) -> List[SearchResult]: 107 | """ 108 | Perform a Quick Spectrum Search of the mass spectral library. 109 | 110 | :param mass_spec: The mass spectrum to search against the library. 111 | :param n_hits: The number of hits to return. 112 | 113 | :return: List of possible identities for the mass spectrum. 114 | """ 115 | 116 | if not isinstance(mass_spec, MassSpectrum): 117 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 118 | 119 | hit_list = _core._spectrum_search(pack(mass_spec, len(mass_spec)))[:n_hits] 120 | 121 | return [SearchResult.from_pynist(hit) for hit in hit_list] 122 | 123 | @staticmethod 124 | def cas_search(cas: str) -> List[SearchResult]: 125 | """ 126 | Search for a compound by CAS number. 127 | 128 | :param cas: 129 | 130 | :return: List of results for CAS number (usually just one result). 131 | """ 132 | 133 | return [SearchResult.from_pynist(hit) for hit in _core._cas_search(cas)] 134 | 135 | @staticmethod 136 | def full_spectrum_search(mass_spec: MassSpectrum, n_hits: int = 5) -> List[SearchResult]: 137 | """ 138 | Perform a Full Spectrum Search of the mass spectral library. 139 | 140 | :param mass_spec: The mass spectrum to search against the library. 141 | :param n_hits: The number of hits to return. 142 | 143 | :return: List of possible identities for the mass spectrum. 144 | """ 145 | 146 | if not isinstance(mass_spec, MassSpectrum): 147 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 148 | 149 | hit_list = _core._full_spectrum_search(pack(mass_spec, len(mass_spec)))[:n_hits] 150 | 151 | return [SearchResult.from_pynist(hit) for hit in hit_list] 152 | 153 | def full_search_with_ref_data( 154 | self, 155 | mass_spec: MassSpectrum, 156 | n_hits: int = 5, 157 | ) -> List[Tuple[SearchResult, ReferenceData]]: 158 | """ 159 | Perform a Full Spectrum Search of the mass spectral library, including reference data. 160 | 161 | :param mass_spec: The mass spectrum to search against the library. 162 | :param n_hits: The number of hits to return. 163 | 164 | :return: List of tuples containing possible identities 165 | for the mass spectrum, and the reference data 166 | """ 167 | 168 | if not isinstance(mass_spec, MassSpectrum): 169 | raise TypeError("`mass_spec` must be a pyms.Spectrum.MassSpectrum object.") 170 | 171 | hit_list = self.full_spectrum_search(mass_spec, n_hits) 172 | 173 | output_buffer = [] 174 | 175 | for idx, hit in enumerate(hit_list): 176 | ref_data = self.get_reference_data(hit.spec_loc) 177 | output_buffer.append((hit, ref_data)) 178 | 179 | return output_buffer 180 | 181 | @staticmethod 182 | def get_reference_data(spec_loc: int) -> ReferenceData: 183 | """ 184 | Get reference data from the library for the compound at the given location. 185 | 186 | :param spec_loc: 187 | """ 188 | 189 | reference_data = _core._get_reference_data(spec_loc) 190 | 191 | return ReferenceData.from_pynist(reference_data) 192 | 193 | def __enter__(self) -> "Engine": 194 | return self 195 | 196 | def __exit__(self, exc_type, exc_val, exc_tb): 197 | self.uninit() 198 | -------------------------------------------------------------------------------- /src/pyms_nist_search/x64/ctnt66_64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x64/ctnt66_64.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x64/nistdl64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x64/nistdl64.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x64/nistdl64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x64/nistdl64.lib -------------------------------------------------------------------------------- /src/pyms_nist_search/x64/nistdl64_2gb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x64/nistdl64_2gb.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x64/nistdl64_2gb.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x64/nistdl64_2gb.lib -------------------------------------------------------------------------------- /src/pyms_nist_search/x86/ctnt66.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x86/ctnt66.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x86/nistdl32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x86/nistdl32.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x86/nistdl32.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x86/nistdl32.lib -------------------------------------------------------------------------------- /src/pyms_nist_search/x86/nistdl32_2gb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x86/nistdl32_2gb.dll -------------------------------------------------------------------------------- /src/pyms_nist_search/x86/nistdl32_2gb.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domdfcoding/pynist/32ccb16cad4e72088674f8b61273e38543e0817b/src/pyms_nist_search/x86/nistdl32_2gb.lib -------------------------------------------------------------------------------- /stubs.txt: -------------------------------------------------------------------------------- 1 | types-requests 2 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import datetime 3 | import sys 4 | 5 | # 3rd party 6 | from pyms.Spectrum import MassSpectrum # type: ignore 7 | 8 | # this package 9 | import pyms_nist_search 10 | 11 | if sys.platform == "win32": 12 | FULL_PATH_TO_MAIN_LIBRARY = "C:\\Users\\dom13\\Python\\mainlib" 13 | FULL_PATH_TO_USER_LIBRARY = "C:\\Users\\dom13\\Python\\01 GitHub Repos\\pynist\\tests\\\\MoNA-export-GC-MS_Spectra" 14 | FULL_PATH_TO_WORK_DIR = "C:\\Users\\dom13\\Python\\00 Projects\\pynist" 15 | else: 16 | FULL_PATH_TO_MAIN_LIBRARY = "/home/domdf/Python/mainlib" 17 | FULL_PATH_TO_USER_LIBRARY = "/home/domdf/Python/01 GitHub Repos/pynist/tests/MoNA-export-GC-MS_Spectra" 18 | FULL_PATH_TO_WORK_DIR = "/home/domdf/Python/00 Projects/pynist" 19 | 20 | # search = pyms_nist_search.Engine( 21 | # FULL_PATH_TO_MAIN_LIBRARY, 22 | # pyms_nist_search.NISTMS_MAIN_LIB, 23 | # FULL_PATH_TO_WORK_DIR, 24 | # debug=True, 25 | # ) 26 | 27 | search = pyms_nist_search.Engine( 28 | FULL_PATH_TO_USER_LIBRARY, 29 | pyms_nist_search.NISTMS_USER_LIB, 30 | FULL_PATH_TO_WORK_DIR, 31 | debug=True, 32 | ) 33 | 34 | mz_int_pairs = [ 35 | (27, 138), 36 | (28, 210), 37 | (32, 59), 38 | (37, 70), 39 | (38, 273), 40 | (39, 895), 41 | (40, 141), 42 | (41, 82), 43 | (50, 710), 44 | (51, 2151), 45 | (52, 434), 46 | (53, 49), 47 | (57, 41), 48 | (59, 121), 49 | (61, 73), 50 | (62, 229), 51 | (63, 703), 52 | (64, 490), 53 | (65, 1106), 54 | (66, 932), 55 | (67, 68), 56 | (70, 159), 57 | (71, 266), 58 | (72, 297), 59 | (73, 44), 60 | (74, 263), 61 | (75, 233), 62 | (76, 330), 63 | (77, 1636), 64 | (78, 294), 65 | (84, 1732), 66 | (87, 70), 67 | (88, 86), 68 | (89, 311), 69 | (90, 155), 70 | (91, 219), 71 | (92, 160), 72 | (93, 107), 73 | (101, 65), 74 | (102, 111), 75 | (103, 99), 76 | (104, 188), 77 | (113, 107), 78 | (114, 120), 79 | (115, 686), 80 | (116, 150), 81 | (117, 91), 82 | (126, 46), 83 | (127, 137), 84 | (128, 201), 85 | (129, 73), 86 | (130, 69), 87 | (139, 447), 88 | (140, 364), 89 | (141, 584), 90 | (142, 279), 91 | (143, 182), 92 | (152, 37), 93 | (153, 60), 94 | (154, 286), 95 | (166, 718), 96 | (167, 3770), 97 | (168, 6825), 98 | (169, 9999), 99 | (170, 1210), 100 | (171, 85), 101 | ] 102 | 103 | mass_list = [] 104 | intensity_list = [] 105 | for mass, intensity in mz_int_pairs: 106 | mass_list.append(mass) 107 | intensity_list.append(intensity) 108 | 109 | mass_spec = MassSpectrum(mass_list, intensity_list) 110 | 111 | start_time = datetime.datetime.now() 112 | print("Performing Full Search") 113 | 114 | hit_list = search.full_search_with_ref_data(mass_spec) 115 | 116 | for hit_no, (hit, ref_data) in enumerate(hit_list): 117 | print(f"Hit {hit_no}") 118 | print(hit) 119 | print(ref_data) 120 | print(ref_data.mass_spec) 121 | print() 122 | 123 | # reference_data = search.get_r#eference_data(hit.spec_loc) 124 | # print(reference_data.mass_spec == ref_data.mass_spec) 125 | # print(reference_data == ref_data) 126 | 127 | print(f"Completed Full Search in {(datetime.datetime.now() - start_time).total_seconds()}") 128 | 129 | ############ 130 | 131 | start_time = datetime.datetime.now() 132 | print("Performing Quick Search") 133 | 134 | hit_list = search.spectrum_search(mass_spec, n_hits=10) 135 | 136 | for hit_no, hit in enumerate(hit_list): 137 | print(f"Hit {hit_no}") 138 | print(hit) 139 | print() 140 | 141 | print(f"Completed Quick Search in {(datetime.datetime.now() - start_time).total_seconds()}") 142 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import os 3 | import sys 4 | 5 | # Remove current dir from sys.path so Python doesn't try to import the source dir 6 | sys.path.remove(os.getcwd()) 7 | 8 | # this package 9 | # from .engines import search # noqa: E402,F401 10 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import pathlib 3 | from typing import Iterator, Optional, Tuple 4 | 5 | # 3rd party 6 | import pytest 7 | import requests 8 | from pyms.Spectrum import MassSpectrum 9 | 10 | # this package 11 | import pyms_nist_search 12 | from pyms_nist_search import ReferenceData 13 | 14 | # this package 15 | from .engines import FULL_PATH_TO_USER_LIBRARY, FULL_PATH_TO_WORK_DIR 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def search() -> Iterator[pyms_nist_search.Engine]: 20 | with pyms_nist_search.Engine( 21 | FULL_PATH_TO_USER_LIBRARY, 22 | pyms_nist_search.NISTMS_USER_LIB, 23 | FULL_PATH_TO_WORK_DIR, # debug=True, 24 | ) as engine: 25 | yield engine 26 | 27 | 28 | @pytest.fixture( 29 | scope="session", 30 | params=[ 31 | "122-39-4", 32 | "71-43-2", 33 | "107-10-8", # "50-37-3", # LSD, shows up under synonym # yapf: ignore 34 | "57-13-6", 35 | # "77-92-9", citric acid, shows up as diisopropyl malonate 36 | # "118-96-7", tnt, being detected as n-sec-butyl-2,4-dinitrobenzenamine 37 | # "67-66-3", # Chloroform, detected as trichloromethane (synonym) 38 | # "‎106-24-1", # Geraniol, being detected as 3,7-dimethyl-2,6-octadien-1-ol(trans) 39 | # "121-14-2", # 2,4-DNT need to fix synonyms 40 | "507-70-0", # Borneol 41 | "78-93-3", # MEK 42 | ] 43 | ) 44 | def spectra(request) -> Tuple[str, Optional[MassSpectrum]]: 45 | # Download required files from NIST Webbook 46 | nist_data_dir = pathlib.Path("nist_jdx_files") 47 | 48 | if not nist_data_dir.exists(): 49 | nist_data_dir.mkdir(parents=True) 50 | 51 | cas = request.param 52 | jcamp_file = nist_data_dir / f"{cas}.jdx" 53 | 54 | if not jcamp_file.exists(): 55 | url = f"https://web.archive.org/web/20201215130403/https://webbook.nist.gov/cgi/cbook.cgi?JCAMP=C{cas.replace('-', '')}&Index=0&Type=Mass" 56 | r = requests.get(url) 57 | if r.status_code != 200: 58 | # Try once more 59 | r = requests.get(url) 60 | r.raise_for_status() 61 | jcamp_file.write_bytes(r.content) 62 | 63 | # Read ReferenceData from Jcamp File 64 | ref_data = ReferenceData.from_jcamp(jcamp_file) 65 | 66 | # Fix for borneol 67 | if ref_data.cas == "507-70-0": 68 | return "Borneol", ref_data.mass_spec 69 | else: 70 | return ref_data.name, ref_data.mass_spec 71 | -------------------------------------------------------------------------------- /tests/constants.py: -------------------------------------------------------------------------------- 1 | test_string = "abcdefg" 2 | test_int = 1234 3 | test_float = 12.34 4 | test_list_ints = [1, 2, 3, 4] 5 | test_list_strs = ['a', 'b', 'c', 'd'] 6 | test_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 4} 7 | 8 | test_numbers = {test_int, test_float} 9 | 10 | test_tuple = (*test_numbers, test_string) 11 | 12 | test_lists = [test_list_ints, test_list_strs] 13 | test_sequences = [*test_lists, test_tuple] 14 | -------------------------------------------------------------------------------- /tests/engines.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import pathlib 3 | 4 | # this package 5 | import pyms_nist_search 6 | 7 | repo_root = pathlib.Path(__file__).parent.parent.absolute() 8 | 9 | FULL_PATH_TO_MAIN_LIBRARY = str(repo_root.parent / "mainlib") 10 | FULL_PATH_TO_USER_LIBRARY = str(repo_root / "MoNA_GCMS_Library" / "MoNA") 11 | FULL_PATH_TO_WORK_DIR = str(repo_root) 12 | 13 | # TODO: tests with mainlib 14 | # search = pyms_nist_search.Engine( 15 | # FULL_PATH_TO_MAIN_LIBRARY, 16 | # pyms_nist_search.NISTMS_MAIN_LIB, 17 | # FULL_PATH_TO_WORK_DIR, 18 | # debug=True, 19 | # ) 20 | 21 | # search = pyms_nist_search.Engine( 22 | # FULL_PATH_TO_USER_LIBRARY, 23 | # pyms_nist_search.NISTMS_USER_LIB, # type: ignore # TODO 24 | # FULL_PATH_TO_WORK_DIR, # debug=True, 25 | # ) 26 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | coincidence>=0.2.0 2 | coverage>=5.1 3 | coverage-pyver-pragma>=0.2.1 4 | domdf-python-tools[testing]>=2.0.1 5 | importlib-metadata>=3.6.0 6 | iniconfig!=1.1.0,>=1.0.1 7 | pytest>=6.0.0 8 | pytest-cov>=2.8.1 9 | pytest-randomly>=3.7.0 10 | pytest-rerunfailures>=9.0 11 | pytest-timeout>=1.4.2 12 | -------------------------------------------------------------------------------- /tests/spectra.py: -------------------------------------------------------------------------------- 1 | # from pyms.Spectrum import MassSpectrum 2 | 3 | # 4 | # mz_int_pairs = [ 5 | # (27, 138), (28, 210), (32, 59), (37, 70), (38, 273), 6 | # (39, 895), (40, 141), (41, 82), (50, 710), (51, 2151), 7 | # (52, 434), (53, 49), (57, 41), (59, 121), (61, 73), 8 | # (62, 229), (63, 703), (64, 490), (65, 1106), (66, 932), 9 | # (67, 68), (70, 159), (71, 266), (72, 297), (73, 44), 10 | # (74, 263), (75, 233), (76, 330), (77, 1636), (78, 294), 11 | # (84, 1732), (87, 70), (88, 86), (89, 311), (90, 155), 12 | # (91, 219), (92, 160), (93, 107), (101, 65), (102, 111), 13 | # (103, 99), (104, 188), (113, 107), (114, 120), (115, 686), 14 | # (116, 150), (117, 91), (126, 46), (127, 137), (128, 201), 15 | # (129, 73), (130, 69), (139, 447), (140, 364), (141, 584), 16 | # (142, 279), (143, 182), (152, 37), (153, 60), (154, 286), 17 | # (166, 718), 18 | # (167, 3770), 19 | # (168, 6825), 20 | # (169, 9999), 21 | # (170, 1210), 22 | # (171, 85), 23 | # ] 24 | # 25 | # 26 | # def make_ms_from_pairs(pairs): 27 | # mass_list = [] 28 | # intensity_list = [] 29 | # for mass, intensity in pairs: 30 | # mass_list.append(mass) 31 | # intensity_list.append(intensity) 32 | # 33 | # return MassSpectrum(mass_list, intensity_list) 34 | 35 | # spectra = { 36 | # # "Diphenylamine": make_ms_from_pairs(mz_int_pairs) 37 | # } 38 | -------------------------------------------------------------------------------- /tests/test_full_search.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | from typing import Optional, Tuple 3 | 4 | # 3rd party 5 | import pytest 6 | from pyms.Spectrum import MassSpectrum 7 | 8 | # this package 9 | import pyms_nist_search 10 | from pyms_nist_search import SearchResult 11 | 12 | 13 | def test_full_search(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 14 | print() 15 | 16 | name, spectrum = spectra 17 | print(f"Testing {name}") 18 | hit_list = search.full_spectrum_search(spectrum, n_hits=5) 19 | 20 | assert len(hit_list) == 5 21 | 22 | for hit in hit_list: 23 | # TODO: test CAS numbers 24 | assert isinstance(hit, SearchResult) 25 | 26 | assert hit_list[0].name.lower() == name.lower() 27 | # assert hit_list[0].cas == cas 28 | 29 | 30 | def test_different_n_hits(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 31 | print() 32 | 33 | name, spectrum = spectra 34 | if name != "Diphenylamine": 35 | pytest.skip() 36 | 37 | for n_hits in range(1, 21): 38 | print(f"Testing with {n_hits} hits") 39 | hit_list = search.full_spectrum_search(spectrum, n_hits=n_hits) 40 | 41 | assert len(hit_list) == n_hits 42 | -------------------------------------------------------------------------------- /tests/test_full_search_ref_data.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | from typing import Optional, Tuple 3 | 4 | # 3rd party 5 | from pyms.Spectrum import MassSpectrum 6 | 7 | # this package 8 | import pyms_nist_search 9 | from pyms_nist_search import ReferenceData, SearchResult 10 | 11 | 12 | def test_full_search(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 13 | print() 14 | 15 | name, spectrum = spectra 16 | print(f"Testing {name}") 17 | hit_list = search.full_search_with_ref_data(spectrum, n_hits=5) 18 | 19 | assert len(hit_list) == 5 20 | 21 | for hit, ref_data in hit_list: 22 | # TODO: test CAS numbers 23 | assert isinstance(hit, SearchResult) 24 | assert isinstance(ref_data, ReferenceData) 25 | assert isinstance(ref_data.mass_spec, MassSpectrum) 26 | 27 | # if hit_list[0][0].name.lower() == name.lower(): 28 | # assert hit_list[0][1].name.lower() == name.lower() 29 | # else: 30 | # assert name.lower() in hit_list[0][1].synonyms 31 | assert hit_list[0][0].name.lower() == name.lower() 32 | assert hit_list[0][1].name.lower() == name.lower() 33 | 34 | 35 | def test_different_n_hits(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 36 | print() 37 | 38 | spectrum = spectra[1] 39 | 40 | for n_hits in range(1, 21): 41 | print(f"Testing with {n_hits} hits") 42 | hit_list = search.full_search_with_ref_data(spectrum, n_hits=n_hits) 43 | 44 | assert len(hit_list) == n_hits 45 | -------------------------------------------------------------------------------- /tests/test_get_ref_data.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | from typing import Optional, Tuple 3 | 4 | # 3rd party 5 | from pyms.Spectrum import MassSpectrum 6 | 7 | # this package 8 | import pyms_nist_search 9 | from pyms_nist_search import ReferenceData, SearchResult 10 | 11 | 12 | def test_get_ref_data(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 13 | print() 14 | 15 | # To avoid duplicates for speed 16 | tested_locs = set() 17 | 18 | # perform a full search first to get some spec_loc values as they may change 19 | name, spectrum = spectra 20 | hit_list = search.full_spectrum_search(spectrum, n_hits=20) 21 | 22 | for hit in hit_list: 23 | assert isinstance(hit, SearchResult) 24 | assert isinstance(hit.spec_loc, int) 25 | if hit.spec_loc not in tested_locs: 26 | print(f"Testing spec_loc {hit.spec_loc}") 27 | 28 | ref_data = search.get_reference_data(hit.spec_loc) 29 | 30 | assert isinstance(ref_data, ReferenceData) 31 | assert isinstance(ref_data.mass_spec, MassSpectrum) 32 | 33 | tested_locs.add(hit.spec_loc) 34 | 35 | assert search.get_reference_data(hit_list[0].spec_loc).name.lower() == name.lower() 36 | # assert search.get_reference_data(hit_list[0].spec_loc).cas == cas 37 | print(search.get_reference_data(hit_list[0].spec_loc)) 38 | print(dict(search.get_reference_data(hit_list[0].spec_loc))) 39 | -------------------------------------------------------------------------------- /tests/test_quick_search.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | from typing import Optional, Tuple 3 | 4 | # 3rd party 5 | import pytest 6 | from pyms.Spectrum import MassSpectrum 7 | 8 | # this package 9 | import pyms_nist_search 10 | from pyms_nist_search import SearchResult 11 | 12 | 13 | def test_quick_search(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 14 | print() 15 | 16 | name, spectrum = spectra 17 | print(f"Testing {name}") 18 | hit_list = search.spectrum_search(spectrum, n_hits=5) 19 | 20 | assert len(hit_list) == 5 21 | 22 | for hit in hit_list: 23 | # TODO: test CAS numbers 24 | assert isinstance(hit, SearchResult) 25 | 26 | # Special case for MEK 27 | if name.lower() == "2-butanone": 28 | assert hit_list[0].name.lower() in { 29 | "2-butanone", 30 | "butanone", 31 | "ethyl methyl ketone", 32 | "methyl ethyl ketone", 33 | } 34 | else: 35 | assert hit_list[0].name.lower() == name.lower() 36 | # assert hit_list[0].cas == cas 37 | 38 | 39 | def test_different_n_hits(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]): 40 | print() 41 | 42 | name, spectrum = spectra 43 | if name != "Diphenylamine": 44 | pytest.skip() 45 | 46 | for n_hits in range(1, 21): 47 | print(f"Testing with {n_hits} hits") 48 | hit_list = search.spectrum_search(spectrum, n_hits=n_hits) 49 | 50 | assert len(hit_list) == n_hits 51 | 52 | 53 | def test_cas_search(search: pyms_nist_search.Engine): 54 | search.cas_search("0-0-0") 55 | -------------------------------------------------------------------------------- /tests/test_reference_data.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import json 3 | import pathlib 4 | import pickle 5 | from typing import Any, Dict, Optional, Tuple 6 | 7 | # 3rd party 8 | import pytest 9 | import sdjson 10 | from pyms.Spectrum import MassSpectrum 11 | 12 | # this package 13 | import pyms_nist_search 14 | from pyms_nist_search import ReferenceData, SearchResult 15 | 16 | # this package 17 | from .constants import ( 18 | test_dictionary, 19 | test_float, 20 | test_int, 21 | test_list_ints, 22 | test_list_strs, 23 | test_lists, 24 | test_numbers, 25 | test_sequences, 26 | test_string, 27 | test_tuple 28 | ) 29 | 30 | 31 | @pytest.fixture() 32 | def reference_data(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]) -> Dict[str, Any]: 33 | 34 | # Get SearchResult and ReferenceData for Diphenylamine 35 | name, spectrum = spectra 36 | if name != "Diphenylamine": 37 | pytest.skip() 38 | hit, ref_data = search.full_search_with_ref_data(spectrum, n_hits=1)[0] 39 | 40 | assert isinstance(hit, SearchResult) 41 | assert isinstance(ref_data, ReferenceData) 42 | assert isinstance(ref_data.mass_spec, MassSpectrum) 43 | 44 | intensity_list = [ # yapf: disable # noqa: E122 45 | 13, 6, 8, 37, 23, 71, 15, 7, 25, 16, 47, 45, 7, 10, 9, 9, 10, 46 | 61, 13, 51, 14, 10, 6, 10, 9, 6, 4, 5, 10, 4, 26, 7, 5, 5, 13, 47 | 4, 5, 16, 12, 27, 16, 10, 12, 27, 178, 329, 999, 137, 8, 48 | ] 49 | 50 | mass_list = [ # yapf: disable # noqa: E122 51 | 18, 28, 38, 39, 50, 51, 52, 62, 63, 64, 65, 66, 71, 72, 74, 75, 52 | 76, 77, 78, 84, 85, 89, 90, 91, 92, 93, 102, 103, 104, 114, 115, 53 | 116, 117, 127, 128, 129, 130, 139, 140, 141, 142, 143, 154, 166, 54 | 167, 168, 169, 170, 171, 55 | ] 56 | 57 | ref_data_dict = { 58 | "name": "DIPHENYLAMINE", 59 | "cas": "0-0-0", 60 | "formula": "C12H11N", 61 | "contributor": "MASS SPECTROSCOPY SOC. OF JAPAN (MSSJ)", 62 | "nist_no": 5698, 63 | "id": "5319", 64 | "mw": 169, 65 | "exact_mass": 169.0, 66 | "synonyms": [], 67 | "mass_spec": { 68 | "intensity_list": intensity_list, 69 | "mass_list": mass_list, 70 | }, 71 | } 72 | 73 | ref_data_dict_non_recursive = { 74 | "name": "DIPHENYLAMINE", 75 | "cas": "0-0-0", 76 | "formula": "C12H11N", 77 | "contributor": "MASS SPECTROSCOPY SOC. OF JAPAN (MSSJ)", 78 | "nist_no": 5698, 79 | "id": "5319", 80 | "mw": 169, 81 | "exact_mass": 169.0, 82 | "synonyms": [], 83 | "mass_spec": ref_data.mass_spec, 84 | } 85 | 86 | ref_data_json = json.dumps(ref_data_dict) 87 | 88 | return { 89 | "ref_data_dict_non_recursive": ref_data_dict_non_recursive, 90 | "ref_data_json": ref_data_json, 91 | "hit": hit, 92 | "ref_data": ref_data, 93 | "ref_data_dict": ref_data_dict, 94 | } 95 | 96 | 97 | def test_json_ref_data(reference_data: Dict[str, Any]): 98 | assert sdjson.dumps(reference_data["ref_data"]) == reference_data["ref_data_json"] 99 | assert reference_data["ref_data"].to_json() == reference_data["ref_data_json"] 100 | assert ReferenceData.from_json(reference_data["ref_data"].to_json()) == reference_data["ref_data"] 101 | 102 | with pytest.raises(json.decoder.JSONDecodeError): 103 | ReferenceData.from_json(test_string) 104 | 105 | for obj in [ 106 | test_int, 107 | test_float, 108 | test_list_ints, 109 | test_list_strs, 110 | test_dictionary, 111 | test_numbers, 112 | test_tuple, 113 | test_lists, 114 | test_sequences, 115 | ]: 116 | with pytest.raises(TypeError): 117 | ReferenceData.from_json(obj) # type: ignore[arg-type] 118 | 119 | 120 | def test_sdjson_ref_data(reference_data: Dict[str, Any]): 121 | assert sdjson.dumps(reference_data["ref_data"]) == reference_data["ref_data_json"] 122 | 123 | 124 | def test_dict(reference_data: Dict[str, Any]): 125 | assert dict(reference_data["ref_data"] 126 | ) == reference_data["ref_data"].to_dict() == reference_data["ref_data_dict_non_recursive"] 127 | assert sdjson.loads(sdjson.dumps(reference_data["ref_data"])) == reference_data["ref_data_dict"] 128 | assert ReferenceData.from_dict(dict(reference_data["ref_data"])) == reference_data["ref_data"] 129 | 130 | for obj in [ 131 | test_string, 132 | test_int, 133 | test_float, 134 | test_list_ints, 135 | test_list_strs, 136 | test_dictionary, 137 | test_numbers, 138 | test_tuple, 139 | test_lists, 140 | test_sequences, 141 | test_lists, 142 | ]: 143 | with pytest.raises(TypeError): 144 | ReferenceData.from_dict(obj) # type: ignore[arg-type] 145 | 146 | 147 | def test_str(reference_data: Dict[str, Any]): 148 | expected_string = "Reference Data: DIPHENYLAMINE \t(0-0-0)" 149 | assert str(reference_data["ref_data"]) == repr(reference_data["ref_data"]) == expected_string 150 | 151 | 152 | def test_eq(reference_data: Dict[str, Any]): 153 | # TODO: make another search result to test equality to 154 | assert reference_data["ref_data"] == reference_data["ref_data"] 155 | 156 | for obj in [ 157 | test_string, 158 | test_int, 159 | test_float, 160 | test_list_ints, 161 | test_list_strs, 162 | test_dictionary, 163 | test_numbers, 164 | test_tuple, 165 | test_lists, 166 | test_sequences, 167 | ]: 168 | assert reference_data["ref_data"] != obj 169 | 170 | assert reference_data["ref_data"] != reference_data["hit"] 171 | assert reference_data["ref_data"] != reference_data["ref_data"].mass_spec 172 | 173 | 174 | def test_pickle(reference_data: Dict[str, Any]): 175 | reloaded_ref_data = pickle.loads(pickle.dumps(reference_data["ref_data"])) # nosec: B301 176 | assert isinstance(reloaded_ref_data, ReferenceData) 177 | assert reloaded_ref_data == reference_data["ref_data"] 178 | 179 | 180 | def test_creation(): 181 | ReferenceData( 182 | name="Compound Name", 183 | cas=112233, 184 | nist_no=123, 185 | id=456, 186 | mw=7.8, 187 | ) 188 | 189 | 190 | def test_from_jcamp(): 191 | # TODO: main bit 192 | 193 | # Errors 194 | for obj in [123, 12.3, (12, 34), set(), dict(), list()]: 195 | with pytest.raises(TypeError): 196 | ReferenceData.from_jcamp(obj) # type: ignore[arg-type] 197 | 198 | with pytest.raises(FileNotFoundError): 199 | ReferenceData.from_jcamp("non-existant_file.jdx") 200 | 201 | with pytest.raises(FileNotFoundError): 202 | ReferenceData.from_jcamp(pathlib.Path("non-existant_file.jdx")) 203 | 204 | 205 | # TODO: from_mona_dict 206 | # TODO: to_msp 207 | -------------------------------------------------------------------------------- /tests/test_search_result.py: -------------------------------------------------------------------------------- 1 | # stdlib 2 | import json 3 | import pickle 4 | from typing import Any, Dict, Optional, Tuple 5 | 6 | # 3rd party 7 | import pytest 8 | import sdjson 9 | from pyms.Spectrum import MassSpectrum 10 | 11 | # this package 12 | import pyms_nist_search 13 | from pyms_nist_search import ReferenceData, SearchResult 14 | 15 | # this package 16 | from .constants import ( 17 | test_dictionary, 18 | test_float, 19 | test_int, 20 | test_list_ints, 21 | test_list_strs, 22 | test_lists, 23 | test_numbers, 24 | test_sequences, 25 | test_string, 26 | test_tuple 27 | ) 28 | 29 | 30 | @pytest.fixture() 31 | def reference_data(search: pyms_nist_search.Engine, spectra: Tuple[str, Optional[MassSpectrum]]) -> Dict[str, Any]: 32 | 33 | # Get SearchResult and ReferenceData for Diphenylamine 34 | name, spectrum = spectra 35 | if name != "Diphenylamine": 36 | pytest.skip() 37 | 38 | hit, ref_data = search.full_search_with_ref_data(spectrum, n_hits=1)[0] 39 | 40 | assert isinstance(hit, SearchResult) 41 | assert isinstance(ref_data, ReferenceData) 42 | assert isinstance(ref_data.mass_spec, MassSpectrum) 43 | 44 | # 45 | # search_res_json = ( 46 | # '{"name": "DIPHENYLAMINE", "cas": "---", "match_factor": 917, "reverse_match_factor": 927, "spec_loc": ' 47 | # '4305039, "hit_prob": 35.08}') 48 | 49 | search_res_dict = { 50 | "name": "DIPHENYLAMINE", 51 | "cas": "---", 52 | "match_factor": 916, 53 | "reverse_match_factor": 926, 54 | "spec_loc": 1046408, 55 | "hit_prob": 35.43, 56 | } 57 | 58 | search_res_json = json.dumps(search_res_dict) 59 | 60 | return { 61 | "search_res_json": search_res_json, 62 | "search_res_dict": search_res_dict, 63 | "hit": hit, 64 | "ref_data": ref_data, 65 | } 66 | 67 | 68 | def test_json_search_result(reference_data: Dict[str, Any]): 69 | assert sdjson.dumps(reference_data["hit"]) == reference_data["search_res_json"] 70 | assert reference_data["hit"].to_json() == reference_data["search_res_json"] 71 | assert SearchResult.from_json(reference_data["hit"].to_json()) == reference_data["hit"] 72 | 73 | with pytest.raises(json.decoder.JSONDecodeError): 74 | ReferenceData.from_json(test_string) 75 | 76 | for obj in [ 77 | test_int, 78 | test_float, 79 | test_list_ints, 80 | test_list_strs, 81 | test_dictionary, 82 | test_numbers, 83 | test_tuple, 84 | test_lists, 85 | test_sequences, 86 | ]: 87 | print(obj) 88 | with pytest.raises(TypeError): 89 | SearchResult.from_json(obj) 90 | 91 | 92 | def test_sdjson_search_result(reference_data: Dict[str, Any]): 93 | assert sdjson.dumps(reference_data["hit"]) == reference_data["search_res_json"] 94 | 95 | 96 | def test_dict(reference_data: Dict[str, Any]): 97 | assert dict(reference_data["hit"]) == reference_data["hit"].to_dict() == reference_data["search_res_dict"] 98 | assert SearchResult.from_dict(dict(reference_data["hit"])) == reference_data["hit"] 99 | # 100 | # with pytest.raises(json.decoder.JSONDecodeError): 101 | # ReferenceData.from_json(test_string) 102 | # 103 | for obj in [ 104 | test_string, 105 | test_int, 106 | test_float, 107 | test_list_ints, 108 | test_list_strs, 109 | test_dictionary, 110 | test_numbers, 111 | test_tuple, 112 | test_lists, 113 | test_sequences, 114 | ]: 115 | with pytest.raises(TypeError): 116 | SearchResult.from_dict(obj) # type: ignore[arg-type] 117 | 118 | 119 | def test_str(reference_data: Dict[str, Any]): 120 | assert str(reference_data["hit"]) == repr(reference_data["hit"]) == "Search Result: DIPHENYLAMINE \t(916)" 121 | 122 | 123 | def test_eq(reference_data: Dict[str, Any]): 124 | # TODO: make another search result to test equality to 125 | assert reference_data["hit"] == reference_data["hit"] 126 | 127 | for obj in [ 128 | test_string, 129 | test_int, 130 | test_float, 131 | test_list_ints, 132 | test_list_strs, 133 | test_dictionary, 134 | test_numbers, 135 | test_tuple, 136 | test_lists, 137 | test_sequences, 138 | ]: 139 | assert reference_data["hit"] != obj 140 | 141 | assert reference_data["hit"] != reference_data["ref_data"] 142 | assert reference_data["hit"] != reference_data["ref_data"].mass_spec 143 | 144 | 145 | def test_pickle(reference_data: Dict[str, Any]): 146 | reloaded_hit = pickle.loads(pickle.dumps(reference_data["hit"])) # nosec: B301 147 | assert isinstance(reloaded_hit, SearchResult) 148 | assert reloaded_hit == reference_data["hit"] 149 | 150 | 151 | def test_creation(): 152 | SearchResult("Compound Name", 112233, 123, 456, 7.8, 999) 153 | SearchResult("Compound Name", "11-22-33", 12.3, 45.6, 78, 99.9) 154 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # 3rd party 2 | import pytest 3 | 4 | # this package 5 | from pyms_nist_search import utils 6 | 7 | 8 | # yapf: disable 9 | def test_parse_name_chars(): 10 | test_strings = { 11 | "Hello World!": [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0], 12 | "The quick brown fox jumps over a lazy dog.": [ 13 | 84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, # noqa: E122 14 | 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, # noqa: E122 15 | 115, 32, 111, 118, 101, 114, 32, 97, 32, 108, 97, 122, 121, # noqa: E122 16 | 32, 100, 111, 103, 46, 0], # noqa: E122 17 | "Pack my box with five dozen liquor jugs": [ 18 | 80, 97, 99, 107, 32, 109, 121, 32, 98, 111, 120, 32, 119, 105, # noqa: E122 19 | 116, 104, 32, 102, 105, 118, 101, 32, 100, 111, 122, 101, 110, # noqa: E122 20 | 32, 108, 105, 113, 117, 111, 114, 32, 106, 117, 103, 115, 0], # noqa: E122 21 | "Mr. Jock, TV quiz PhD, bags few lynx": [ 22 | 77, 114, 46, 32, 74, 111, 99, 107, 44, 32, 84, 86, 32, 113, # noqa: E122 23 | 117, 105, 122, 32, 80, 104, 68, 44, 32, 98, 97, 103, 115, 32, # noqa: E122 24 | 102, 101, 119, 32, 108, 121, 110, 120, 0], # noqa: E122 25 | "α-Zearalenol": [224, 45, 90, 101, 97, 114, 97, 108, 101, 110, 111, 108, 0], 26 | "β-Carotene": [225, 45, 67, 97, 114, 111, 116, 101, 110, 101, 0], 27 | "γ-Butyrolactone": [231, 45, 66, 117, 116, 121, 114, 111, 108, 97, 99, 116, 111, 110, 101, 0], 28 | "δ-Tocopherol": [235, 45, 84, 111, 99, 111, 112, 104, 101, 114, 111, 108, 0], 29 | } 30 | # yapf: enable 31 | 32 | for ascii, codes in test_strings.items(): # noqa: A001 # pylint: disable=redefined-builtin 33 | assert utils.parse_name_chars(codes) == ascii 34 | 35 | # Extra 0s at end: 36 | for i in range(5): 37 | for ascii, codes in test_strings.items(): # noqa: A001 # pylint: disable=redefined-builtin 38 | assert utils.parse_name_chars(codes + ([0] * i)) == ascii 39 | 40 | # Test remaining special characters 41 | 42 | assert utils.parse_name_chars([238, 0]) == 'ε' 43 | assert utils.parse_name_chars([227, 0]) == 'π' 44 | assert utils.parse_name_chars([229, 0]) == 'σ' 45 | assert utils.parse_name_chars([230, 0]) == 'μ' 46 | assert utils.parse_name_chars([234, 0]) == 'ω' 47 | assert utils.parse_name_chars([241, 0]) == '±' 48 | assert utils.parse_name_chars([252, 0]) == 'η' 49 | 50 | # Test unrecognised characters 51 | for i in range(-31, 0): 52 | with pytest.warns(UserWarning): 53 | assert utils.parse_name_chars([i, 0]) == '�' 54 | 55 | # Test secret message 56 | assert utils.parse_name_chars([ 57 | 78, 58 | 111, 59 | 116, 60 | 104, 61 | 105, 62 | 110, 63 | 103, 64 | 32, 65 | 84, 66 | 111, 67 | 32, 68 | 83, 69 | 101, 70 | 101, 71 | 32, 72 | 72, 73 | 101, 74 | 114, 75 | 101, 76 | 0, 77 | 65, 78 | 108, 79 | 97, 80 | 110, 81 | 32, 82 | 84, 83 | 117, 84 | 114, 85 | 105, 86 | 110, 87 | 103, 88 | 0 89 | ]) == "Nothing To See Here" 90 | 91 | # Errors 92 | with pytest.raises(TypeError): 93 | utils.parse_name_chars(['a', 'b', 'c']) # type: ignore[list-item] 94 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # This file is managed by 'repo_helper'. 2 | # You may add new sections, but any changes made to the following sections will be lost: 3 | # * tox 4 | # * envlists 5 | # * testenv:.package 6 | # * testenv:py313-dev 7 | # * testenv:py313 8 | # * testenv:py312-dev 9 | # * testenv:py312 10 | # * testenv:docs 11 | # * testenv:build 12 | # * testenv:lint 13 | # * testenv:perflint 14 | # * testenv:mypy 15 | # * testenv:pyup 16 | # * testenv:coverage 17 | # * flake8 18 | # * coverage:run 19 | # * coverage:report 20 | # * check-wheel-contents 21 | # * pytest 22 | 23 | [tox] 24 | envlist = py37, py38, py39, py310, py311, py312, mypy, build 25 | skip_missing_interpreters = True 26 | isolated_build = True 27 | requires = 28 | pip>=21,!=22.2 29 | tox-envlist>=0.2.1 30 | tox~=3.0 31 | virtualenv!=20.16.0 32 | 33 | [envlists] 34 | test = py37, py38, py39, py310, py311, py312 35 | qa = mypy, lint 36 | cov = py38, coverage 37 | 38 | [testenv:.package] 39 | setenv = 40 | PYTHONDEVMODE=1 41 | PIP_DISABLE_PIP_VERSION_CHECK=1 42 | 43 | [testenv:py312] 44 | download = True 45 | setenv = 46 | PYTHONDEVMODE=1 47 | PIP_DISABLE_PIP_VERSION_CHECK=1 48 | 49 | [testenv:docs] 50 | setenv = SHOW_TODOS = 1 51 | passenv = SPHINX_BUILDER 52 | basepython = python3.8 53 | changedir = {toxinidir}/doc-source 54 | deps = -r{toxinidir}/doc-source/requirements.txt 55 | commands = sphinx-build -M {env:SPHINX_BUILDER:html} . ./build {posargs} 56 | 57 | [testenv:build] 58 | setenv = 59 | PYTHONDEVMODE=1 60 | PIP_DISABLE_PIP_VERSION_CHECK=1 61 | PIP_PREFER_BINARY=1 62 | UNSAFE_PYO3_SKIP_VERSION_CHECK=1 63 | skip_install = True 64 | changedir = {toxinidir} 65 | deps = 66 | build[virtualenv]>=0.3.1 67 | check-wheel-contents>=0.1.0 68 | twine>=3.2.0 69 | cryptography<40; implementation_name == "pypy" and python_version <= "3.7" 70 | commands = 71 | python -m build --sdist --wheel "{toxinidir}" 72 | twine check dist/*.tar.gz dist/*.whl 73 | check-wheel-contents dist/ 74 | 75 | [testenv:lint] 76 | basepython = python3.8 77 | changedir = {toxinidir} 78 | ignore_errors = True 79 | skip_install = True 80 | deps = 81 | flake8>=3.8.2,<5 82 | flake8-2020>=1.6.0 83 | flake8-builtins>=1.5.3 84 | flake8-docstrings>=1.5.0 85 | flake8-dunder-all>=0.1.1 86 | flake8-encodings>=0.1.0 87 | flake8-github-actions>=0.1.0 88 | flake8-noqa>=1.1.0,<=1.2.2 89 | flake8-pyi>=20.10.0,<=22.8.0 90 | flake8-pytest-style>=1.3.0,<2 91 | flake8-quotes>=3.3.0 92 | flake8-slots>=0.1.0 93 | flake8-sphinx-links>=0.0.4 94 | flake8-strftime>=0.1.1 95 | flake8-typing-imports>=1.10.0 96 | git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git 97 | git+https://github.com/domdfcoding/flake8-rst-docstrings.git 98 | git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods 99 | git+https://github.com/python-formate/flake8-missing-annotations.git 100 | pydocstyle>=6.0.0 101 | pygments>=2.7.1 102 | importlib_metadata<4.5.0; python_version<'3.8' 103 | commands = python3 -m flake8_rst_docstrings_sphinx src/pyms_nist_search tests MoNA_GCMS_Library --allow-toolbox {posargs} 104 | 105 | [testenv:perflint] 106 | basepython = python3.8 107 | changedir = {toxinidir} 108 | ignore_errors = True 109 | skip_install = True 110 | deps = perflint 111 | commands = python3 -m perflint pyms_nist_search {posargs} 112 | 113 | [testenv:mypy] 114 | basepython = python3.8 115 | ignore_errors = True 116 | changedir = {toxinidir} 117 | deps = 118 | mypy==1.8.0 119 | -r{toxinidir}/tests/requirements.txt 120 | -r{toxinidir}/stubs.txt 121 | commands = mypy src/pyms_nist_search tests MoNA_GCMS_Library {posargs} 122 | 123 | [testenv:pyup] 124 | basepython = python3.8 125 | skip_install = True 126 | ignore_errors = True 127 | changedir = {toxinidir} 128 | deps = pyupgrade-directories 129 | commands = pyup_dirs src/pyms_nist_search tests MoNA_GCMS_Library --py36-plus --recursive 130 | 131 | [testenv:coverage] 132 | basepython = python3.8 133 | skip_install = True 134 | ignore_errors = True 135 | whitelist_externals = /bin/bash 136 | passenv = 137 | COV_PYTHON_VERSION 138 | COV_PLATFORM 139 | COV_PYTHON_IMPLEMENTATION 140 | * 141 | changedir = {toxinidir} 142 | deps = 143 | coverage>=5 144 | coverage_pyver_pragma>=0.2.1 145 | commands = 146 | /bin/bash -c "rm -rf htmlcov" 147 | coverage html 148 | /bin/bash -c "DISPLAY=:0 firefox 'htmlcov/index.html'" 149 | 150 | [flake8] 151 | max-line-length = 120 152 | select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 ENC011 ENC012 ENC021 ENC022 ENC023 ENC024 ENC025 ENC026 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 NQA001 NQA002 NQA003 NQA004 NQA005 NQA102 NQA103 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 153 | extend-exclude = doc-source,old,build,dist,__pkginfo__.py,setup.py,venv 154 | rst-directives = 155 | TODO 156 | envvar 157 | extras-require 158 | license 159 | license-info 160 | rst-roles = choosealicense 161 | per-file-ignores = 162 | tests/*: D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 163 | */*.pyi: E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 164 | pytest-parametrize-names-type = csv 165 | inline-quotes = " 166 | multiline-quotes = """ 167 | docstring-quotes = """ 168 | count = True 169 | min_python_version = 3.7 170 | unused-arguments-ignore-abstract-functions = True 171 | unused-arguments-ignore-overload-functions = True 172 | unused-arguments-ignore-magic-methods = True 173 | unused-arguments-ignore-variadic-names = True 174 | 175 | [coverage:run] 176 | plugins = coverage_pyver_pragma 177 | 178 | [coverage:report] 179 | fail_under = 80 180 | show_missing = True 181 | exclude_lines = 182 | raise AssertionError 183 | raise NotImplementedError 184 | if 0: 185 | if False: 186 | if TYPE_CHECKING 187 | if typing.TYPE_CHECKING 188 | if __name__ == .__main__.: 189 | 190 | [check-wheel-contents] 191 | ignore = W002 192 | toplevel = pyms_nist_search 193 | 194 | [pytest] 195 | addopts = --color yes --durations 25 196 | timeout = 300 197 | 198 | [testenv] 199 | setenv = 200 | PYTHONDEVMODE=1 201 | PIP_DISABLE_PIP_VERSION_CHECK=1 202 | SETUPTOOLS_USE_DISTUTILS="stdlib" 203 | passenv = 204 | DOCKER_HOST 205 | PIP_PREFER_BINARY 206 | deps = -r{toxinidir}/tests/requirements.txt 207 | commands = 208 | python --version 209 | python -m pytest --cov=pyms_nist_search -r aR tests/ {posargs} 210 | python coverage-fixup.py 211 | 212 | [coverage:paths] 213 | source = 214 | src 215 | .tox/*/site-packages 216 | --------------------------------------------------------------------------------