diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..fdb04ba --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,37 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [created] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build package + run: python setup.py sdist bdist_wheel + - name: Publish package + run: twine upload dist/* + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.pypi_api_token }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd84155 --- /dev/null +++ b/.gitignore @@ -0,0 +1,330 @@ +## Windows.gitignore + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +## Linux.gitignore + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +## macOS.gitignore + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +## VisualStudioCode.gitignore + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +## JetBrains.gitignore + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +## Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..60f3762 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Nie Mingzhao + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cf1b9cf --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE + +graft */static diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..67984f3 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: init tests docs build clean + +init: + pip install -r requirements.txt + +tests: + python scripts/tests.py + +docs: + python scripts/docs.py + +build: + python setup.py sdist bdist_wheel + +clean: + python scripts/clean.py diff --git a/README.md b/README.md index 75c81f1..1c80c92 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # ramkit + An analysis kernel for raman spectrum. + +## Start + +```cmd +python -m venv venv +venv\Scripts\activate.bat (cmd.exe) +venv\Scripts\Activate.ps1 (PowerShell) +source venv/bin/activate (bash/zsh) +pip install -r requirements.txt +... +deactivate +``` diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..5128596 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..f883c2d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- Project information ----------------------------------------------------- + +project = 'ramkit' +copyright = '2018, Nie Mingzhao' +author = 'Nie Mingzhao' + +# The short X.Y version +version = '0.2.0' +# The full version, including alpha/beta/rc tags +release = '0.2.0' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.todo', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinxdoc' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..c117104 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,19 @@ +.. You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to this documentation! +============================== + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + modules + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..7893348 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/extras/.gitkeep b/extras/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ramkit/__init__.py b/ramkit/__init__.py new file mode 100644 index 0000000..f3594c2 --- /dev/null +++ b/ramkit/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from .baseline import * +from .curvefitting import * +from .filters import * +from .functions import * +from .mlregressor import * +from .normalization import * +from .peakseeking import * +from .spectranization import * diff --git a/ramkit/baseline.py b/ramkit/baseline.py new file mode 100644 index 0000000..777fd51 --- /dev/null +++ b/ramkit/baseline.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +""" +Baseline Processing Functions. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +from .filters import smooth + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + + +def baseline(x, n=21, method='SWMA', **kwargs): + """ + Estimate Baseline. + + Parameters + ---------- + x : 1d-ndarray + Input data. + n : int + Size of segment, only for 'SWMA' method. + method : str {'SWMA', 'arPLS'} + Method to use for estimation. + kwargs : + Keyword arguments passed to method used for estimation. + For 'arPLS' method: + * 'lam' -- float, the lambda smoothness parameter, typical values are + between 10**2 to 10**9, default is 10**5 + * 'ratio' -- float, the ratio parameter, default is 10**-6 + + Returns + ------- + result : 1d-ndarray + Output baseline data. + """ + if method == 'SWMA': + x, win = smooth(x, n), 3 + x_c, x_t = (x,) * 2 + while True: + x_t = x_c.copy() + x_c = signal.savgol_filter(x_t, win, 0) + res = x_t - x_c + std = np.std(res, ddof=1) + cur = np.where(res > std) + if not len(cur[0]): + break + x_t[cur] = x_c[cur] + x_c, win = x_t, win + 2 + baseline = x_c + elif method == 'arPLS': + lam = kwargs.get('lam', 1e5) + ratio = kwargs.get('ratio', 1e-6) + N = len(x) + D = sparse.csc_matrix(np.diff(np.eye(N), 2)) + w = np.ones(N) + while True: + W = sparse.spdiags(w, 0, N, N) + Z = W + lam * D.dot(D.transpose()) + z = sparse.linalg.spsolve(Z, w * x) + d = x - z + dn = d[d < 0] + m = np.mean(dn) + s = np.std(dn) + wt = 1 / (1 + np.exp(2 * (d - (2 * s - m)) / s)) + if np.linalg.norm(w - wt) / np.linalg.norm(w) < ratio: + break + w = wt + baseline = z + else: + baseline = x.copy() + return baseline diff --git a/ramkit/curvefitting.py b/ramkit/curvefitting.py new file mode 100644 index 0000000..65cf1f8 --- /dev/null +++ b/ramkit/curvefitting.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +""" +Curve Fitting Functions. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + + +def curve_fit(func, x, y, n=(1, 1), *args, **kwargs): + """ + Curve Fitting. + + Parameters + ---------- + func : callable + Base model function to fit. + x : 1d-ndarray + Input data of x-axis. + y : 1d-ndarray + Input data of y-axis. + n : 2-tuple (peak_num, param_num) + Setting of multi-peak params. + * 'peak_num' -- int, the guess number of peaks + * 'param_num' -- int, the number of params of each peak + p0 : list + Initial guess for the params. + bounds : 2-tuple of list + Lower and upper bounds on params. + method : str {'lm', 'trf', 'dogbox'} + Method to use for optimization. + kwargs : + Keyword arguments passed to 'scipy.optimize.curve_fit'. + + Returns + ------- + popt : 1d-ndarray + Optimal values for the params so that the sum of the squared + residuals of 'func(x, *popt) - y' is minimized. + pcov : 2d-ndarray + The estimated covariance of popt. To compute one standard deviation + errors on the params use 'perr = np.sqrt(np.diag(pcov))'. + + Examples + -------- + >>> import numpy as np + >>> import ramkit as rk + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(-5, 5, 100) + >>> y = rk.lorentzian(x) + rk.gaussian(x, xc=1) + >>> popt, pcov = rk.curve_fit(rk.gaussian, x, y, (2, 3), + ... p0=[1, 0, 1, 1, 1, 1]) + >>> # popt, pcov = rk.curve_fit(rk.gaussian, x, y, (2, 3), + ... # bounds=([0, -1, 0, 0, -1, 0], + ... # [1, 1, 1, 1, 1, 1])) + >>> perr = np.sqrt(np.diag(pcov)) + >>> plt.plot(x, y, 'k') + >>> plt.plot(x, rk.gaussian(x, *popt[:3]), 'r') + >>> plt.plot(x, rk.gaussian(x, *popt[3:]), 'b') + """ + s1 = 'def func_(x_,%s):args_=[tuple(i) for i in t([%s],%d)];return %s' + s2 = ','.join(['a' + str(i) for i in range(n[0] * n[1])]) + s3 = '+'.join(['f(x_,*args_[' + str(i) + '])' for i in range(n[0])]) + s4 = {'t': lambda x, n: [x[i:i+n] for i in range(0, len(x), n)], 'f': func} + exec(s1 % (s2, s2, n[1], s3), s4, locals()) + return optimize.curve_fit(locals()['func_'], x, y, *args, **kwargs) diff --git a/ramkit/filters.py b/ramkit/filters.py new file mode 100644 index 0000000..4ee6ff9 --- /dev/null +++ b/ramkit/filters.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +""" +Wave Filtering Functions (Smoothing etc.). + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +from .spectranization import calc_nsd + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + + +def smooth(x, n=21): + """ + Smooth Data. + + Parameters + ---------- + x : 1d-ndarray + Input data. + n : int + Size of segment. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + x_c, x_t = (x,) * 2 + while True: + x_t = x_c.copy() + x_c = signal.savgol_filter(x_t, 3, 0) + nsd = calc_nsd(x_t, n) + res = np.sum((x_t - x_c) ** 2 / nsd ** 2) + if res >= len(x_t): + break + return x_c diff --git a/ramkit/functions.py b/ramkit/functions.py new file mode 100644 index 0000000..7a782a0 --- /dev/null +++ b/ramkit/functions.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- + +""" +Basic Mathematical Functions. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + + +def constant(x, a=0): + """ + Constant Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a : float + Intercept. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + return 0 * x + a + + +def linear(x, a=1, b=0): + """ + Linear Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a : float + Slope. + b : float + Intercept. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + return a * x + b + + +def parabola(x, a=1, b=0, c=0): + """ + Parabola Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a,b,c : float + Coefficients. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + return a * x ** 2 + b * x + c + + +def cubic(x, a=1, b=0, c=0, d=0): + """ + Cubic Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a,b,c,d : float + Coefficients. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + return a * x ** 3 + b * x ** 2 + c * x + d + + +def gaussian(x, a=1, xc=0, w=1, y0=0): + """ + Gaussian Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a : float + Area. + xc : float + Center. + w : float + FWHM. + y0 : float + Base. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + sigma = w / (2 * np.sqrt(2 * np.log(2))) + y1 = -np.power(x - xc, 2) / (2 * np.power(sigma, 2)) + return y0 + a / (sigma * np.sqrt(2 * np.pi)) * np.exp(y1) + + +def lorentzian(x, a=1, xc=0, w=1, y0=0): + """ + Lorentzian Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a : float + Area. + xc : float + Center. + w : float + FWHM. + y0 : float + Base. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + gamma = w / 2 + y1 = np.power(x - xc, 2) + np.power(gamma, 2) + return y0 + a / np.pi * gamma / y1 + + +def voigt(x, a=1, xc=0, wg=1, wl=1, y0=0): + """ + Voigt Function. + + Parameters + ---------- + x : 1d-ndarray + Input data. + a : float + Area. + xc : float + Center. + wg : float + FWHM of gaussian. + wl : float + FWHM of lorentzian. + y0 : float + Base. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + sigma, gamma = wg / (2 * np.sqrt(2 * np.log(2))), wl / 2 + y1 = special.wofz((x - xc + 1j * gamma) / (sigma * np.sqrt(2))) + return y0 + a / (sigma * np.sqrt(2 * np.pi)) * np.real(y1) diff --git a/ramkit/mlregressor.py b/ramkit/mlregressor.py new file mode 100644 index 0000000..2fe6289 --- /dev/null +++ b/ramkit/mlregressor.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +Regression Analysis Functions and Other Machine Learning Algorithms. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False diff --git a/ramkit/normalization.py b/ramkit/normalization.py new file mode 100644 index 0000000..d264245 --- /dev/null +++ b/ramkit/normalization.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +Normalization Functions. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False diff --git a/ramkit/peakseeking.py b/ramkit/peakseeking.py new file mode 100644 index 0000000..9f2744d --- /dev/null +++ b/ramkit/peakseeking.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +""" +Peak Seeking Functions. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False diff --git a/ramkit/spectranization.py b/ramkit/spectranization.py new file mode 100644 index 0000000..2dddfab --- /dev/null +++ b/ramkit/spectranization.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +""" +Spectral Data Conversion and Transform Processing. + +>>> import ramkit as rk +""" + +import matplotlib.pyplot as plt +import numpy as np +from scipy import optimize, signal, sparse, special + +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + + +def calc_nsd(x, n=21): + """ + Estimate Noise Standard Deviation of Data. + + Parameters + ---------- + x : 1d-ndarray + Input data. + n : int + Size of segment. + + Returns + ------- + result : float + Value of noise standard deviation. + """ + x_diff = np.diff(x, n=2) + x_frag = np.array_split(x_diff, len(x_diff) // n) + cursor = np.argmin([np.std(i, ddof=1) for i in x_frag]) + for i in range(n * (cursor + 1), len(x_diff)): + i_frag = x_diff[i-n:i-1] + i_frag_avg = np.mean(i_frag) + i_frag_std = np.std(i_frag, ddof=1) + if np.abs(x_diff[i] - i_frag_avg) > 3 * i_frag_std: + x_diff[i] = i_frag_avg + for i in range(0, n * cursor - 1)[::-1]: + if n * cursor - 1 < 0: + break + i_frag = x_diff[i+1:i+n] + i_frag_avg = np.mean(i_frag) + i_frag_std = np.std(i_frag, ddof=1) + if np.abs(x_diff[i] - i_frag_avg) > 3 * i_frag_std: + x_diff[i] = i_frag_avg + return np.std(x_diff, ddof=1) / 6 ** 0.5 + + +def remove_spike(x): + """ + Remove Spikes of Data. + + Parameters + ---------- + x : 1d-ndarray + Input data. + + Returns + ------- + result : 1d-ndarray + Output data. + """ + x_c = signal.savgol_filter(x, 3, 0) + res = x - x_c + std = np.std(res, ddof=1) + cur = np.where(res > 3.5 * std) + cur = [range(i - 1, i + 2) for i in cur[0]] + x_x = np.array(range(len(x))) + x_m = np.delete(x_x, cur) + x_n = np.delete(x, cur) + return np.interp(x_x, x_m, x_n) diff --git a/ramkit/static/.gitkeep b/ramkit/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..224c707 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,66 @@ +alabaster==0.7.12 +astroid==2.11.2 +autopep8==1.6.0 +Babel==2.9.1 +bleach==4.1.0 +certifi==2021.10.8 +chardet==4.0.0 +charset-normalizer==2.0.12 +colorama==0.4.4 +cycler==0.11.0 +dill==0.3.4 +docutils==0.17.1 +idna==3.3 +imagesize==1.3.0 +importlib-metadata==4.8.3 +importlib-resources==5.4.0 +isort==5.10.1 +Jinja2==3.0.3 +joblib==1.1.0 +keyring==23.4.1 +kiwisolver==1.3.1 +lazy-object-proxy==1.7.1 +MarkupSafe==2.0.1 +matplotlib==3.3.4 +mccabe==0.7.0 +mpmath==1.2.1 +numpy==1.21.0 +packaging==21.3 +pandas==1.1.5 +Pillow==9.0.1 +pkginfo==1.8.2 +platformdirs==2.4.0 +pycodestyle==2.8.0 +Pygments==2.11.2 +pylint==2.13.5 +pyparsing==3.0.8 +python-dateutil==2.8.2 +pytz==2022.1 +pywin32-ctypes==0.2.0 +readme-renderer==34.0 +requests==2.27.1 +requests-toolbelt==0.9.1 +rfc3986==1.5.0 +scikit-learn==0.24.2 +scipy==1.5.4 +six==1.16.0 +snowballstemmer==2.2.0 +Sphinx==4.5.0 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +sympy==1.9 +threadpoolctl==3.1.0 +toml==0.10.2 +tomli==1.2.3 +tqdm==4.64.0 +twine==3.8.0 +typed-ast==1.5.2 +typing_extensions==4.1.1 +urllib3==1.26.9 +webencodings==0.5.1 +wrapt==1.14.0 +zipp==3.6.0 diff --git a/scripts/clean.py b/scripts/clean.py new file mode 100644 index 0000000..fc73671 --- /dev/null +++ b/scripts/clean.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import shutil + +print('=============================CLEAN====================================') + +for root, dirs, files in os.walk(os.getcwd()): + if root.startswith(os.path.abspath('venv')): + continue + for name in filter(lambda x: x == '__pycache__', dirs): + shutil.rmtree(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: root == os.getcwd() and x == 'build', dirs): + shutil.rmtree(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: root == os.getcwd() and x == 'dist', dirs): + shutil.rmtree(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.egg-info'), dirs): + shutil.rmtree(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.pyc'), files): + os.remove(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.pyo'), files): + os.remove(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.pyd'), files): + os.remove(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.spec'), files): + os.remove(os.path.abspath(os.path.join(root, name))) + for name in filter(lambda x: x.endswith('.log'), files): + os.remove(os.path.abspath(os.path.join(root, name))) diff --git a/scripts/docs.py b/scripts/docs.py new file mode 100644 index 0000000..532b9d5 --- /dev/null +++ b/scripts/docs.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os + +print('=============================DOCS=====================================') + +reply = input('> Rerun autodoc to insert docstrings from modules (y/N) [n]:') +if reply == 'y': + os.system('sphinx-apidoc -P -f -o docs . tests setup.py') +os.system('cd docs && make clean && make html') diff --git a/scripts/tests.py b/scripts/tests.py new file mode 100644 index 0000000..00a26c5 --- /dev/null +++ b/scripts/tests.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import doctest +import os + +from setuptools import find_packages + +print('============================DOCTEST===================================') + +for package in find_packages(exclude=['tests']): + for root, dirs, files in os.walk(package): + for name in filter(lambda x: x.endswith('.py'), files): + name = os.path.abspath(os.path.join(root, name)) + doctest.testfile(name, module_relative=False, verbose=True) + +print('============================UNITTEST==================================') + +os.system('python -m unittest discover -v') diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c852b9b --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from setuptools import find_packages, setup + +with open('README.md', 'r') as f: + long_description = f.read() + +config = { + 'name': 'ramkit', # Required + 'version': '0.2.0', # Required + 'description': 'An analysis kernel for raman spectrum.', # Required + 'long_description': long_description, + 'long_description_content_type': 'text/markdown', + 'author': 'Nie Mingzhao', + 'author_email': '1432440963@qq.com', + 'url': 'https://github.com/niemingzhao/ramkit', + 'download_url': 'https://github.com/niemingzhao/ramkit/archive/master.zip', + 'keywords': 'raman spectrum spectroscopy algorithm', + 'platforms': 'any', + 'license': 'MIT', + 'classifiers': [ + 'Development Status :: 2 - Pre-Alpha', + 'Environment :: Console', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.8', + 'Topic :: Scientific/Engineering :: Physics' + ], + + 'packages': find_packages(exclude=['tests']), # Required + 'include_package_data': True, + 'python_requires': '~=3.8', + 'install_requires': ['numpy', + 'sympy', + 'scipy', + 'pandas', + 'matplotlib', + 'scikit-learn'] +} + +setup(**config) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/_context.py b/tests/_context.py new file mode 100644 index 0000000..ea5ba38 --- /dev/null +++ b/tests/_context.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +__all__ = [] + +import os +import sys + +from setuptools import find_packages + +sys.path.insert(0, os.path.abspath('..')) + +for package in find_packages(exclude=['tests']): + locals()[package] = __import__(package) + __all__.append(package) diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..4ca5359 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +import unittest + +from ._context import * + + +class TestSuite(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_basic(self): + pass + + +if __name__ == '__main__': + unittest.main()