├── Changelog ├── INSTALL.txt ├── LICENSE.txt ├── README ├── TODO ├── common.py ├── docs ├── DCT ├── Makefile ├── ext │ ├── docscrape.py │ ├── docscrape_sphinx.py │ ├── docscraper.py │ ├── numpydoc.py │ └── only_directives.py └── src │ ├── .static │ └── .gitignore │ ├── conf.py │ ├── examples │ ├── arspec_phoneme.py │ ├── periodogram_1.py │ ├── periodogram_2.py │ ├── voice-womanKP-01.wav │ └── yop.m │ ├── index.rst │ ├── intro.rst │ ├── lpc.rst │ └── spectral.rst ├── pavement.py ├── scikits ├── __init__.py └── talkbox │ ├── __init__.py │ ├── features │ ├── __init__.py │ ├── mel.py │ ├── mfcc.py │ └── setup.py │ ├── linpred │ ├── __init__.py │ ├── common.py │ ├── levinson_lpc.py │ ├── py_lpc.py │ ├── setup.py │ ├── src │ │ ├── _lpc.c │ │ ├── levinson.c │ │ └── levinson.h │ └── tests │ │ ├── gentest.m │ │ └── test_lpc.py │ ├── misc │ ├── __init__.py │ ├── peak_picking.py │ ├── setup.py │ └── tests │ │ └── test_find_peaks.py │ ├── setup.py │ ├── spectral │ ├── __init__.py │ ├── basic.py │ ├── r_examples.R │ └── tests │ │ └── test_basic.py │ ├── tools │ ├── __init__.py │ ├── correlations.py │ ├── ffilter.py │ ├── preprocess.py │ ├── segmentaxis.py │ ├── setup.py │ ├── src │ │ ├── SConstruct │ │ ├── acorr.py │ │ ├── cacorr.c │ │ ├── cacorr.pyx │ │ ├── cffilter.c │ │ ├── cffilter.pyx │ │ └── test.py │ └── tests │ │ ├── test_correlations.py │ │ ├── test_ffilter.py │ │ ├── test_preprocess.py │ │ └── test_segmentaxis.py │ └── transforms │ ├── __init__.py │ ├── dct.py │ ├── dct_ref.py │ ├── setup.py │ └── tests │ └── test_dct.py ├── setup.cfg └── setup.py /Changelog: -------------------------------------------------------------------------------- 1 | Thu, 26 Mar 2009 23:06:25 +0900 2 | * Fix missing files in the tarball 3 | 4 | Tue, 17 Feb 2009 17:06:53 +0900 5 | * Fix a serious bug in LPC wrapper: this should fix segfaults on 64 6 | bits arch. 7 | 8 | Sun, 18 Jan 2009 20:55:54 +0900 9 | * Implemented MFCC computation 10 | * Assumes scipy 0.8 installed (for the DCT) 11 | 12 | Tue, 25 Nov 2008 18:14:52 +0900 13 | * Release 0.2.1 14 | 15 | Sat, 22 Nov 2008 11:01:03 +0900 16 | * Add a cython version of direct implementation (wo FFT) 17 | autocorrelation 18 | * Add argument to optionally use direct autocorrelation in lpcres; 19 | this makes lpcres several times faster for typical LPC order/windows 20 | size ration in speech processing. 21 | 22 | Fri, 21 Nov 2008 20:59:57 +0900 23 | * Add a function slfilter in tools to filter a set of windows 24 | * Add a cython version of slfilter 25 | * Use slfilter to implement rank 2 of linpred 26 | 27 | Wed, 5 Nov 2008 04:13:05 +0900 28 | * Fix some major memory leaks in levinson C code 29 | * Fix DCT with fft backend 30 | * Release 0.2 31 | 32 | Thu, 25 Sep 2008 01:38:44 +0900 33 | * Fix bug in levinson error computation (data given to 34 | PyArray_NewFromData was not allocated on the heap). 35 | * Autocorrelation now is the biased estimator (fix discrepancy with 36 | matlab for lpc) 37 | * Initial version of arspec 38 | * Better error handling of input errors in C levinson 39 | 40 | Tue, 23 Sep 2008 01:50:27 +0900 41 | 42 | * levinson is implemented in both python and C (C only support real 43 | double; python version can do complex numbers) 44 | * lpc implemented (support axis argument) 45 | * simple periodogram implemented 46 | * 0.1 release 47 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | .. vim:syntax=rest 2 | 3 | Installing 4 | ========== 5 | 6 | To install talkbox, you just need: 7 | 8 | python setup.py install 9 | 10 | If you want to install in a non-standard path: 11 | 12 | python setup.py install --prefix=$MYPATH 13 | 14 | Suported platforms: 15 | ------------------- 16 | 17 | tallkbox should build and run on any platform running numpy and scipy. 18 | 19 | Requirements 20 | ------------ 21 | 22 | To run correctly, talkbox requires the following softwares: 23 | 24 | - python (> 2.4): http://www.python.org 25 | - setuptools: 26 | http://peak.telecommunity.com/DevCenter/setuptools#installing-setuptools 27 | - numpy: http://www.scipy.org/ 28 | - scipy: http://www.scipy.org/ 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Cournapeau David 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | talkbox is a scikit for signal/speech processing, to extend scipy capabilities 2 | in that domain. 3 | 4 | See INSTALL.txt for installation instruction. 5 | 6 | The goal is to provide some functionalities found in matlab signal toolbox, as 7 | well as other features not found in matlab for speech processing. In 8 | particular, we intend to implement the following: 9 | - parametric and non parametric spectral estimation: ar, periodogram, 10 | MUSIC, PENCIL, etc... 11 | - lpc estimation 12 | - Discrete Cosine Transform, Modified Discrete Cosine Transform 13 | - basic speech-related features: mfcc, mel filtering, etc... 14 | 15 | One perticular feature of talkbox is that every algorithm will have a 100% 16 | python implementation for educational purpose; some implementations will also 17 | be in C, but there alway will be a python reference implementation. 18 | 19 | talkbox is licensed under a very liberal, BSD-based license, for use in both 20 | open-source and proprietary softwares. 21 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Release 0.2 2 | =========== 3 | 4 | Spectrogram estimation: 5 | - tapping, daniel smoothing should work (See Bloomfield) 6 | - add plotting capabilities to periodogram 7 | - AR spectrum estimation (again with plotting) 8 | 9 | Documentation for periodogram + examples 10 | Documentation for LPC + examples 11 | Documentation for Levinson 12 | 13 | Release 0.3 14 | =========== 15 | 16 | Implement real cepstrum 17 | ... 18 | 19 | Release 1.0 20 | =========== 21 | 22 | Have major transform implemented: dct II/IV, MDCT. 23 | Have periodogram, aryule and at least one high-resolution method for spectrum 24 | (MUSIC ?) 25 | Have good-performance LPC-analysis. 26 | 27 | Future 28 | ====== 29 | 30 | - More parametric spectrum estimation 31 | - Polyphase filters ? 32 | 33 | References 34 | =========== 35 | 36 | [1] Marple, S.L. Digital Spectral Analysis, Englewood Cliffs, NJ, 37 | Prentice-Hall, 1987, pp. 373-378. (for eigen-like method) 38 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | descr = """Talkbox, to make your numpy environment speech aware ! 2 | 3 | Talkbox is set of python modules for speech/signal processing. The goal of this 4 | toolbox is to be a sandbox for features which may end up in scipy at some 5 | point. The following features are planned before a 1.0 release: 6 | 7 | * Spectrum estimation related functions: both parametic (lpc, high 8 | resolution methods like music and co), and non-parametric (Welch, 9 | periodogram) 10 | * Fourier-like transforms (DCT, DST, MDCT, etc...) 11 | * Basic signal processing tasks such as resampling 12 | * Speech related functionalities: mfcc, mel spectrum, etc.. 13 | * More as it comes 14 | 15 | I want talkbox to be useful for both research and educational purpose. As such, 16 | a requirement is to have a pure python implementation for everything - for 17 | educational purpose and reproducibility - and optional C for speed.""" 18 | 19 | DISTNAME = 'scikits.talkbox' 20 | DESCRIPTION = 'Talkbox, a set of python modules for speech/signal processing' 21 | LONG_DESCRIPTION = descr 22 | MAINTAINER = 'David Cournapeau', 23 | MAINTAINER_EMAIL = 'david@ar.media.kyoto-u.ac.jp', 24 | URL = 'http://projects.scipy.org/scipy/scikits' 25 | LICENSE = 'MIT' 26 | DOWNLOAD_URL = URL 27 | 28 | MAJOR = 0 29 | MINOR = 2 30 | MICRO = 3 31 | DEV = False 32 | 33 | CLASSIFIERS = [ 'Development Status :: 1 - Planning', 34 | 'Environment :: Console', 35 | 'Intended Audience :: Developers', 36 | 'Intended Audience :: Science/Research', 37 | 'License :: OSI Approved :: BSD License', 38 | 'Topic :: Scientific/Engineering'] 39 | 40 | def build_verstring(): 41 | return '%d.%d.%d' % (MAJOR, MINOR, MICRO) 42 | 43 | def build_fverstring(): 44 | if DEV: 45 | return build_verstring() + '.dev' 46 | else: 47 | return build_verstring() 48 | 49 | def write_version(fname): 50 | f = open(fname, "w") 51 | f.write(""" 52 | short_version='%s' 53 | version=short_version 54 | dev=%s 55 | if dev: 56 | version += '.dev' 57 | """ % (build_verstring(), DEV)) 58 | f.close() 59 | 60 | VERSION = build_fverstring() 61 | INSTALL_REQUIRE = 'numpy' 62 | -------------------------------------------------------------------------------- /docs/DCT: -------------------------------------------------------------------------------- 1 | About DCT, on http://www.dsprelated.com/showmessage/7328/1.php: 2 | 3 | > I am trying to use IFFT to calculate the IDCT(Type 4). Can any one 4 | > please explain how I am supposed to go about it. I am familiar with 5 | > the procedure required to evaluate IDCT (Type I ) using IFFT but 6 | > cannot seem to figure out how it is going to work for IDCT Type - 4. 7 | 8 | Our FFTW library (www.fftw.org) has an implementation of the DCT-IV 9 | that works like this (i.e., via an equal size real-data FFT). There 10 | are a few different algorithms to choose from. The simplest that I 11 | know of is: 12 | 13 | S. C. Chan and K. L. Ho, "Direct methods for computing discrete 14 | sinusoidal transforms," IEE Proceedings F 137 (6), 433-442 (1990). 15 | 16 | which re-expresses it as an equal-size DCT-II or DCT-III, which you 17 | can then re-express as a real-data DFT by a variety of means, e.g. the 18 | one on FFTPACK, also described in John Makhoul, "A fast cosine 19 | transform in one and two dimensions," IEEE Trans. on Acoust. Speech 20 | and Sig. Proc., ASSP-28 (1), 27-34 (1980). However, this Chan/Ho 21 | algorithm has a serious flaw: its rms numerical errors grow as 22 | O(sqrt(n)), vs. O(sqrt(log n)) for the Cooley-Tukey FFT. 23 | 24 | To get O(sqrt(log n)) errors for the DCT-IV for *even* sizes, FFTW 25 | uses the method from: 26 | 27 | Zhongde Wang, "On computing the discrete Fourier and cosine 28 | transforms," IEEE Trans. Acoust. Speech Sig. Proc. ASSP-33 (4), 29 | 1341-1344 (1985). 30 | 31 | to re-express it as a pair of DCT-III (or DCT-II) problems, which are 32 | then solved as above. For accurate treatment of odd sizes, we use 33 | the algorithm from S. C. Chan and K. L. Ho, "Fast algorithms for 34 | computing the discrete cosine transform," IEEE Trans. Circuits Systems 35 | II: Analog & Digital Sig. Proc. 39 (3), 185-190 (1992) [ this paper 36 | has errors, however...see our source code ]. 37 | 38 | The problem of algorithms with poor numerical characteristics seems to 39 | be widespread in the DCT literature. For example, the standard 40 | algorithm to re-express the DCT-I as a real-FFT of the same size (used 41 | in FFTPACK, Numerical Recipes, etc.), is also "unstable" (i.e., 42 | O(sqrt(n)) errors). On the other hand, you are usually safe with any 43 | algorithm that is essentially Cooley-Tukey specialized for the 44 | symmetries of the DCT (algorithms that recursively break a DCT into 45 | smaller DCTs tend to be of this type). 46 | 47 | Of course, FFTPACK has been around for 30 years and I've never heard 48 | of anyone complain about the O(sqrt(n)) errors in its DCT-I code. 49 | Probably, this is because most people only compute DCTs of relatively 50 | small datasets. 51 | 52 | Cordially, 53 | Steven G. Johnson 54 | 55 | PS. For people in this thread wondering what a DCT IV is, see e.g. 56 | http://en.wikipedia.org/wiki/Discrete_cosine_transform ...the 57 | different types of DCT all correspond to DFTs of real-even data, with 58 | various half-sample shifts in the input and output, with 59 | correspondingly different boundary conditions. The main application 60 | of the DCT-IV that I've seen is for the MDCT (any DCT-IV algorithm 61 | trivially gives you an MDCT algorithm), but I'd be interested in 62 | hearing of other uses. 63 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) src 13 | 14 | .PHONY: help clean html web pickle htmlhelp latex changes linkcheck 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " pickle to make pickle files (usable by e.g. sphinx-web)" 20 | @echo " htmlhelp to make HTML files and a HTML help project" 21 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 22 | @echo " changes to make an overview over all changed/added/deprecated items" 23 | @echo " linkcheck to check all external links for integrity" 24 | 25 | clean: 26 | -rm -rf build/* 27 | 28 | html: 29 | mkdir -p build/html build/doctrees 30 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html 31 | @echo 32 | @echo "Build finished. The HTML pages are in build/html." 33 | 34 | pickle: 35 | mkdir -p build/pickle build/doctrees 36 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle 37 | @echo 38 | @echo "Build finished; now you can process the pickle files or run" 39 | @echo " sphinx-web build/pickle" 40 | @echo "to start the sphinx-web server." 41 | 42 | web: pickle 43 | 44 | htmlhelp: 45 | mkdir -p build/htmlhelp build/doctrees 46 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp 47 | @echo 48 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 49 | ".hhp project file in build/htmlhelp." 50 | 51 | latex: 52 | mkdir -p build/latex build/doctrees 53 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex 54 | @echo 55 | @echo "Build finished; the LaTeX files are in build/latex." 56 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 57 | "run these through (pdf)latex." 58 | 59 | changes: 60 | mkdir -p build/changes build/doctrees 61 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes 62 | @echo 63 | @echo "The overview file is in build/changes." 64 | 65 | linkcheck: 66 | mkdir -p build/linkcheck build/doctrees 67 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck 68 | @echo 69 | @echo "Link check complete; look for any errors in the above output " \ 70 | "or in build/linkcheck/output.txt." 71 | -------------------------------------------------------------------------------- /docs/ext/docscrape.py: -------------------------------------------------------------------------------- 1 | """Extract reference documentation from the NumPy source tree. 2 | 3 | """ 4 | 5 | import inspect 6 | import textwrap 7 | import re 8 | from StringIO import StringIO 9 | 10 | class Reader(object): 11 | """A line-based string reader. 12 | 13 | """ 14 | def __init__(self, data): 15 | """ 16 | Parameters 17 | ---------- 18 | data : str 19 | String with lines separated by '\n'. 20 | 21 | """ 22 | if isinstance(data,list): 23 | self._str = data 24 | else: 25 | self._str = data.split('\n') # store string as list of lines 26 | 27 | self.reset() 28 | 29 | def __getitem__(self, n): 30 | return self._str[n] 31 | 32 | def reset(self): 33 | self._l = 0 # current line nr 34 | 35 | def read(self): 36 | if not self.eof(): 37 | out = self[self._l] 38 | self._l += 1 39 | return out 40 | else: 41 | return '' 42 | 43 | def seek_next_non_empty_line(self): 44 | for l in self[self._l:]: 45 | if l.strip(): 46 | break 47 | else: 48 | self._l += 1 49 | 50 | def eof(self): 51 | return self._l >= len(self._str) 52 | 53 | def read_to_condition(self, condition_func): 54 | start = self._l 55 | for line in self[start:]: 56 | if condition_func(line): 57 | return self[start:self._l] 58 | self._l += 1 59 | if self.eof(): 60 | return self[start:self._l+1] 61 | return [] 62 | 63 | def read_to_next_empty_line(self): 64 | self.seek_next_non_empty_line() 65 | def is_empty(line): 66 | return not line.strip() 67 | return self.read_to_condition(is_empty) 68 | 69 | def read_to_next_unindented_line(self): 70 | def is_unindented(line): 71 | return (line.strip() and (len(line.lstrip()) == len(line))) 72 | return self.read_to_condition(is_unindented) 73 | 74 | def peek(self,n=0): 75 | if self._l + n < len(self._str): 76 | return self[self._l + n] 77 | else: 78 | return '' 79 | 80 | def is_empty(self): 81 | return not ''.join(self._str).strip() 82 | 83 | 84 | class NumpyDocString(object): 85 | def __init__(self,docstring): 86 | docstring = docstring.split('\n') 87 | 88 | # De-indent paragraph 89 | try: 90 | indent = min(len(s) - len(s.lstrip()) for s in docstring 91 | if s.strip()) 92 | except ValueError: 93 | indent = 0 94 | 95 | for n,line in enumerate(docstring): 96 | docstring[n] = docstring[n][indent:] 97 | 98 | self._doc = Reader(docstring) 99 | self._parsed_data = { 100 | 'Signature': '', 101 | 'Summary': '', 102 | 'Extended Summary': [], 103 | 'Parameters': [], 104 | 'Returns': [], 105 | 'Raises': [], 106 | 'Warns': [], 107 | 'Other Parameters': [], 108 | 'Attributes': [], 109 | 'Methods': [], 110 | 'See Also': [], 111 | 'Notes': [], 112 | 'References': '', 113 | 'Examples': '', 114 | 'index': {} 115 | } 116 | 117 | self._parse() 118 | 119 | def __getitem__(self,key): 120 | return self._parsed_data[key] 121 | 122 | def __setitem__(self,key,val): 123 | if not self._parsed_data.has_key(key): 124 | raise ValueError("Unknown section %s" % key) 125 | else: 126 | self._parsed_data[key] = val 127 | 128 | def _is_at_section(self): 129 | self._doc.seek_next_non_empty_line() 130 | 131 | if self._doc.eof(): 132 | return False 133 | 134 | l1 = self._doc.peek().strip() # e.g. Parameters 135 | 136 | if l1.startswith('.. index::'): 137 | return True 138 | 139 | l2 = self._doc.peek(1).strip() # ---------- 140 | return (len(l1) == len(l2) and l2 == '-'*len(l1)) 141 | 142 | def _strip(self,doc): 143 | i = 0 144 | j = 0 145 | for i,line in enumerate(doc): 146 | if line.strip(): break 147 | 148 | for j,line in enumerate(doc[::-1]): 149 | if line.strip(): break 150 | 151 | return doc[i:len(doc)-j] 152 | 153 | def _read_to_next_section(self): 154 | section = self._doc.read_to_next_empty_line() 155 | 156 | while not self._is_at_section() and not self._doc.eof(): 157 | if not self._doc.peek(-1).strip(): # previous line was empty 158 | section += [''] 159 | 160 | section += self._doc.read_to_next_empty_line() 161 | 162 | return section 163 | 164 | def _read_sections(self): 165 | while not self._doc.eof(): 166 | data = self._read_to_next_section() 167 | name = data[0].strip() 168 | 169 | if name.startswith('..'): # index section 170 | yield name, data[1:] 171 | elif len(data) < 2: 172 | yield StopIteration 173 | else: 174 | yield name, self._strip(data[2:]) 175 | 176 | def _parse_param_list(self,content): 177 | r = Reader(content) 178 | params = [] 179 | while not r.eof(): 180 | header = r.read().strip() 181 | if ' : ' in header: 182 | arg_name, arg_type = header.split(' : ')[:2] 183 | else: 184 | arg_name, arg_type = header, '' 185 | 186 | desc = r.read_to_next_unindented_line() 187 | for n,line in enumerate(desc): 188 | desc[n] = line.strip() 189 | desc = desc #'\n'.join(desc) 190 | 191 | params.append((arg_name,arg_type,desc)) 192 | 193 | return params 194 | 195 | def _parse_see_also(self, content): 196 | """ 197 | func_name : Descriptive text 198 | continued text 199 | another_func_name : Descriptive text 200 | func_name1, func_name2, func_name3 201 | 202 | """ 203 | functions = [] 204 | current_func = None 205 | rest = [] 206 | for line in content: 207 | if not line.strip(): continue 208 | if ':' in line: 209 | if current_func: 210 | functions.append((current_func, rest)) 211 | r = line.split(':', 1) 212 | current_func = r[0].strip() 213 | r[1] = r[1].strip() 214 | if r[1]: 215 | rest = [r[1]] 216 | else: 217 | rest = [] 218 | elif not line.startswith(' '): 219 | if current_func: 220 | functions.append((current_func, rest)) 221 | current_func = None 222 | rest = [] 223 | if ',' in line: 224 | for func in line.split(','): 225 | func = func.strip() 226 | if func: 227 | functions.append((func, [])) 228 | elif line.strip(): 229 | current_func = line.strip() 230 | elif current_func is not None: 231 | rest.append(line.strip()) 232 | if current_func: 233 | functions.append((current_func, rest)) 234 | return functions 235 | 236 | def _parse_index(self, section, content): 237 | """ 238 | .. index: default 239 | :refguide: something, else, and more 240 | 241 | """ 242 | def strip_each_in(lst): 243 | return [s.strip() for s in lst] 244 | 245 | out = {} 246 | section = section.split('::') 247 | if len(section) > 1: 248 | out['default'] = strip_each_in(section[1].split(','))[0] 249 | for line in content: 250 | line = line.split(':') 251 | if len(line) > 2: 252 | out[line[1]] = strip_each_in(line[2].split(',')) 253 | return out 254 | 255 | def _parse(self): 256 | self._doc.reset() 257 | 258 | # grab signature (if given) and summary 259 | if not self._is_at_section(): 260 | summary = self._doc.read_to_next_empty_line() 261 | else: 262 | summary = [] 263 | 264 | if len(summary) == 1 and \ 265 | re.compile('^[\w\.]+\(.*\)$').match(summary[0].strip()): 266 | self['Signature'] = summary[0] 267 | if not self._is_at_section(): 268 | self['Summary'] = self._doc.read_to_next_empty_line() 269 | else: 270 | self['Summary'] = summary 271 | 272 | if not self._is_at_section(): 273 | self['Extended Summary'] = self._read_to_next_section() 274 | 275 | for (section,content) in self._read_sections(): 276 | if not section.startswith('..'): 277 | section = ' '.join([s.capitalize() for s in section.split(' ')]) 278 | if section in ('Parameters', 'Attributes', 'Methods', 279 | 'Returns', 'Raises', 'Warns'): 280 | self[section] = self._parse_param_list(content) 281 | elif section.startswith('.. index::'): 282 | self['index'] = self._parse_index(section, content) 283 | elif section == 'See Also': 284 | self['See Also'] = self._parse_see_also(content) 285 | else: 286 | self[section] = content 287 | 288 | # string conversion routines 289 | 290 | def _str_header(self, name, symbol='-'): 291 | return [name, len(name)*symbol] 292 | 293 | def _str_indent(self, doc, indent=4): 294 | out = [] 295 | for line in doc: 296 | out += [' '*indent + line] 297 | return out 298 | 299 | def _str_signature(self): 300 | if self['Signature']: 301 | return [self['Signature'].replace('*','\*')] + [''] 302 | else: 303 | return [''] 304 | 305 | def _str_summary(self): 306 | if self['Summary']: 307 | return self['Summary'] + [''] 308 | else: 309 | return [] 310 | 311 | def _str_extended_summary(self): 312 | if self['Extended Summary']: 313 | return self['Extended Summary'] + [''] 314 | else: 315 | return [] 316 | 317 | def _str_param_list(self, name): 318 | out = [] 319 | if self[name]: 320 | out += self._str_header(name) 321 | for param,param_type,desc in self[name]: 322 | out += ['%s : %s' % (param, param_type)] 323 | out += self._str_indent(desc) 324 | out += [''] 325 | return out 326 | 327 | def _str_section(self, name): 328 | out = [] 329 | if self[name]: 330 | out += self._str_header(name) 331 | out += self[name] 332 | out += [''] 333 | return out 334 | 335 | def _str_see_also(self, func_role): 336 | if not self['See Also']: return [] 337 | out = [] 338 | out += self._str_header("See Also") 339 | last_had_desc = True 340 | for func, desc in self['See Also']: 341 | if func_role: 342 | link = ':%s:`%s`' % (func_role, func) 343 | else: 344 | link = "`%s`_" % func 345 | if desc or last_had_desc: 346 | out += [''] 347 | out += [link] 348 | else: 349 | out[-1] += ", %s" % link 350 | if desc: 351 | out += self._str_indent(desc) 352 | last_had_desc = True 353 | else: 354 | last_had_desc = False 355 | out += [''] 356 | return out 357 | 358 | def _str_index(self): 359 | idx = self['index'] 360 | out = [] 361 | out += ['.. index:: %s' % idx.get('default','')] 362 | for section, references in idx.iteritems(): 363 | if section == 'default': 364 | continue 365 | out += [' :%s: %s' % (section, ', '.join(references))] 366 | return out 367 | 368 | def __str__(self, func_role=''): 369 | out = [] 370 | out += self._str_signature() 371 | out += self._str_summary() 372 | out += self._str_extended_summary() 373 | for param_list in ('Parameters','Returns','Raises'): 374 | out += self._str_param_list(param_list) 375 | out += self._str_see_also(func_role) 376 | for s in ('Notes','References','Examples'): 377 | out += self._str_section(s) 378 | out += self._str_index() 379 | return '\n'.join(out) 380 | 381 | 382 | def indent(str,indent=4): 383 | indent_str = ' '*indent 384 | if str is None: 385 | return indent_str 386 | lines = str.split('\n') 387 | return '\n'.join(indent_str + l for l in lines) 388 | 389 | def header(text, style='-'): 390 | return text + '\n' + style*len(text) + '\n' 391 | 392 | 393 | class FunctionDoc(NumpyDocString): 394 | def __init__(self, func, role='func'): 395 | self._f = func 396 | self._role = role # e.g. "func" or "meth" 397 | try: 398 | NumpyDocString.__init__(self,inspect.getdoc(func) or '') 399 | except ValueError, e: 400 | print '*'*78 401 | print "ERROR: '%s' while parsing `%s`" % (e, self._f) 402 | print '*'*78 403 | #print "Docstring follows:" 404 | #print doclines 405 | #print '='*78 406 | 407 | def __str__(self): 408 | out = '' 409 | 410 | func_name = getattr(self._f, '__name__', self.__class__.__name__) 411 | if hasattr(self._f, '__class__') or inspect.isclass(self._f): 412 | func = getattr(self._f, '__call__', self._f.__init__) 413 | else: 414 | func = self._f 415 | 416 | if self['Signature']: 417 | signature = self['Signature'] 418 | else: 419 | try: 420 | # try to read signature 421 | argspec = inspect.getargspec(func) 422 | argspec = inspect.formatargspec(*argspec) 423 | argspec = argspec.replace('*','\*') 424 | signature = '%s%s' % (func_name, argspec) 425 | except TypeError, e: 426 | signature = '%s()' % func_name 427 | self['Signature'] = signature 428 | 429 | signature = signature.replace('*', '\*') 430 | 431 | roles = {'func': 'function', 432 | 'meth': 'method'} 433 | 434 | if self._role: 435 | if not roles.has_key(self._role): 436 | print "Warning: invalid role %s" % self._role 437 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), 438 | func_name) 439 | 440 | out += super(FunctionDoc, self).__str__(func_role=self._role) 441 | return out 442 | 443 | 444 | class ClassDoc(NumpyDocString): 445 | def __init__(self,cls,modulename='',func_doc=FunctionDoc): 446 | if not inspect.isclass(cls): 447 | raise ValueError("Initialise using a class. Got %r" % cls) 448 | self._cls = cls 449 | 450 | if modulename and not modulename.endswith('.'): 451 | modulename += '.' 452 | self._mod = modulename 453 | self._name = cls.__name__ 454 | self._func_doc = func_doc 455 | 456 | NumpyDocString.__init__(self, cls.__doc__) 457 | 458 | @property 459 | def methods(self): 460 | return [name for name,func in inspect.getmembers(self._cls) 461 | if not name.startswith('_') and callable(func)] 462 | 463 | def __str__(self): 464 | out = '' 465 | out += super(ClassDoc, self).__str__() 466 | out += "\n\n" 467 | 468 | #for m in self.methods: 469 | # print "Parsing `%s`" % m 470 | # out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n' 471 | # out += '.. index::\n single: %s; %s\n\n' % (self._name, m) 472 | 473 | return out 474 | 475 | 476 | -------------------------------------------------------------------------------- /docs/ext/docscrape_sphinx.py: -------------------------------------------------------------------------------- 1 | import re 2 | from docscrape import NumpyDocString, FunctionDoc, ClassDoc 3 | 4 | class SphinxDocString(NumpyDocString): 5 | # string conversion routines 6 | def _str_header(self, name, symbol='`'): 7 | return ['**' + name + '**:', ''] 8 | 9 | def _str_field_list(self, name): 10 | return [':' + name + ':'] 11 | 12 | def _str_indent(self, doc, indent=4): 13 | out = [] 14 | for line in doc: 15 | out += [' '*indent + line] 16 | return out 17 | 18 | def _str_signature(self): 19 | return [''] 20 | if self['Signature']: 21 | return ['``%s``' % self['Signature']] + [''] 22 | else: 23 | return [''] 24 | 25 | def _str_summary(self): 26 | return self['Summary'] + [''] 27 | 28 | def _str_extended_summary(self): 29 | return self['Extended Summary'] + [''] 30 | 31 | def _str_param_list(self, name): 32 | out = [] 33 | if self[name]: 34 | out += self._str_field_list(name) 35 | out += [''] 36 | for param,param_type,desc in self[name]: 37 | out += self._str_indent(['**%s** : %s' % (param, param_type)]) 38 | out += [''] 39 | out += self._str_indent(desc,8) 40 | out += [''] 41 | return out 42 | 43 | def _str_section(self, name): 44 | out = [] 45 | if self[name]: 46 | out += self._str_header(name) 47 | out += [''] 48 | content = self._str_indent(self[name]) 49 | out += content 50 | out += [''] 51 | return out 52 | 53 | def _str_see_also(self, func_role): 54 | out = [] 55 | if self['See Also']: 56 | see_also = super(SphinxDocString, self)._str_see_also(func_role) 57 | out = ['.. seealso::', ''] 58 | out += self._str_indent(see_also[2:]) 59 | return out 60 | 61 | def _str_index(self): 62 | idx = self['index'] 63 | out = [] 64 | if len(idx) == 0: 65 | return out 66 | 67 | out += ['.. index:: %s' % idx.get('default','')] 68 | for section, references in idx.iteritems(): 69 | if section == 'default': 70 | continue 71 | elif section == 'refguide': 72 | out += [' single: %s' % (', '.join(references))] 73 | else: 74 | out += [' %s: %s' % (section, ','.join(references))] 75 | return out 76 | 77 | def _str_references(self): 78 | out = [] 79 | if self['References']: 80 | out += self._str_header('References') 81 | if isinstance(self['References'], str): 82 | self['References'] = [self['References']] 83 | out.extend(self['References']) 84 | out += [''] 85 | return out 86 | 87 | def __str__(self, indent=0, func_role="func"): 88 | out = [] 89 | out += self._str_signature() 90 | out += self._str_index() + [''] 91 | out += self._str_summary() 92 | out += self._str_extended_summary() 93 | for param_list in ('Parameters', 'Attributes', 'Methods', 94 | 'Returns','Raises'): 95 | out += self._str_param_list(param_list) 96 | out += self._str_see_also("obj") 97 | out += self._str_section('Notes') 98 | out += self._str_references() 99 | out += self._str_section('Examples') 100 | out = self._str_indent(out,indent) 101 | return '\n'.join(out) 102 | 103 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 104 | pass 105 | 106 | class SphinxClassDoc(SphinxDocString, ClassDoc): 107 | pass 108 | -------------------------------------------------------------------------------- /docs/ext/docscraper.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import re 3 | from warnings import warn 4 | 5 | from docutils.statemachine import StringList 6 | 7 | import sphinx.ext.autodoc as autodoc 8 | 9 | 10 | #--- Defaults ----------------------------------------------------------------- 11 | # Define some defaults. Ideally, a user should be able to change them by setting 12 | # some options in conf.py. 13 | 14 | # How to format the name of a variable in a list 15 | default_name_format = "**%s**" 16 | # How to format the type of a variable in a list 17 | default_type_format = "*%s*" 18 | # How should we format the blocks ? 19 | default_block_format = None 20 | # How should we format list/descriptoins ? 21 | default_list_format = "field" 22 | 23 | 24 | #--- Regular expressions ------------------------------------------------------ 25 | empty_rgx = re.compile(r"^\s*$") 26 | arguments_rgx = re.compile(r"^([a-zA-Z]\w*)(=\w+)?$") 27 | pysig_rgx = re.compile(r"""(?P[a-zA-Z]\w*[.])? 28 | (?P[a-zA-Z]\w*)\s* 29 | \((?P.*)\)\s* 30 | (\s* -> \s* .*)? 31 | """, re.VERBOSE) 32 | directive_rgx = re.compile(r'''^\s* # Some indentation 33 | [.]{2} # .. 34 | \s(?P\w+) # directive name 35 | ::\s* # :: 36 | (?P\w.+)*$ # directive argument 37 | ''', re.VERBOSE) 38 | description_rgx = re.compile('^(?P\w+[^:]*)(?:\s+:\s+(?P.*))?$') 39 | doctest_rgx = re.compile(r'>>>( +|$)') 40 | field_rgx = re.compile(r"\s*:(?P\w+):\s*(?P.+)?") 41 | 42 | # Synonyms 43 | synonyms = {'attribute': 'Attributes', 44 | 'attributes': 'Attributes', 45 | # 46 | 'describe': 'describe', 47 | # 48 | 'example': 'Examples', 49 | 'examples': 'Examples', 50 | # 51 | 'index': 'index', 52 | # 53 | 'note': 'Notes', 54 | 'notes': 'Notes', 55 | # 56 | 'other parameter': 'Other Parameters', 57 | 'other parameters': 'Other Parameters', 58 | # 59 | 'parameter': 'Parameters', 60 | 'parameters': 'Parameters', 61 | # 62 | 'raise': 'Raises', 63 | 'raises': 'Raises', 64 | # 65 | 'reference': 'References', 66 | 'references': 'References', 67 | # 68 | 'return': 'Returns', 69 | 'returns': 'Returns', 70 | # 71 | 'see also': 'See Also', 72 | 'seealso': 'See Also', 73 | # 74 | 'summary':'Summary', 75 | 'extended summary': 'Extended Summary', 76 | # 77 | 'warn': 'Warns', 78 | 'warns': 'Warns', 79 | # 80 | 'warning': 'Warnings', 81 | 'warnings': 'Warnings', 82 | } 83 | 84 | 85 | #--- Builders ----------------------------------------------------------------- 86 | 87 | class BlockFormatter(object): 88 | """Defines a class of functions to format sections (blocks). 89 | An instance is initialized by giving a type of output (`block_type`) and 90 | optionally a delimitor. 91 | The instance can then be called with a header and some contents. 92 | 93 | :Attributes: 94 | **block_type** : {'topic','rubric','describe','field','section'}, string 95 | Output type for a block. For example, 'section' will output each block 96 | as a separate section, while 'field' will output the block as a list 97 | of fields... 98 | **delimitor**: {'~'}, string 99 | Character used for the delimitation of the header and the content for 100 | a section. 101 | 102 | """ 103 | # 104 | def __init__(self, block_type, delimitor='~'): 105 | if block_type == 'topic': 106 | formatstr = "\n\n.. topic:: %s\n" 107 | tab = 0 108 | elif block_type == 'rubric': 109 | formatstr = "\n\n.. rubric:: %s\n\n" 110 | tab = 0 111 | elif block_type == 'describe': 112 | formatstr = "\n\n.. describe:: %s\n\n\n" 113 | tab = 3 114 | elif block_type == 'field': 115 | formatstr = "\n\n:%s:\n\n" 116 | tab = 3 117 | elif block_type == 'section': 118 | formatstr = "\n\n%s\n%s\n\n" 119 | tab = 0 120 | else: 121 | formatstr = "**%s**:\n\n\n" 122 | tab = 3 123 | self.format_string = formatstr 124 | self.tab = tab 125 | self.delim = delimitor 126 | # 127 | def __call__(self, header, content): 128 | try: 129 | output = Docstring(self.format_string % header) 130 | except TypeError: 131 | output = Docstring(self.format_string % (header, 132 | self.delim * len(header))) 133 | output += Docstring(content).indent(self.tab) 134 | return output 135 | 136 | #--- Generic functions ------------------------------------------------------- 137 | 138 | 139 | class Docstring(list): 140 | """A subclass of the standard Python list, with some improved functionalities. 141 | Basically, this class is a simpler version of ``docutils.StringList``, without 142 | parent tracking. 143 | 144 | """ 145 | # 146 | def __init__(self, text): 147 | # 148 | # Skip the pre-processing if we already have a Docstring 149 | if isinstance(text, Docstring): 150 | rawdoc = text 151 | # Use autodoc.prepare_docstring on strings 152 | elif isinstance(text, basestring): 153 | rawdoc = autodoc.prepare_docstring(text) 154 | # Careful: prepare_docstring adds an extra blank line... 155 | rawdoc.pop(-1) 156 | # A StringList or a list: make sure that we don't start with empty lines 157 | elif isinstance(text, (list, StringList)): 158 | rawdoc = text 159 | while rawdoc and not rawdoc[0]: 160 | rawdoc.pop(0) 161 | # 162 | list.__init__(self, rawdoc) 163 | # 164 | def __str__(self): 165 | return "\n".join(self) 166 | # 167 | def __getslice__(self, start, end): 168 | # When slicing a Docstring, we must get a Docstring 169 | return Docstring(list.__getslice__(self, start, end)) 170 | # 171 | def first_non_empty(self, start=0): 172 | """Finds the first non empty line after the line `start` (included). 173 | 174 | """ 175 | i = start 176 | while (i < len(self)-1) and empty_rgx.match(self[i]): 177 | i += 1 178 | return self[i] 179 | # 180 | def deindent(self, n=None, start=0, end=None): 181 | """ 182 | Removes the first `n` spaces from the beginning of each line between 183 | `start` and `end`. 184 | 185 | :param n: Number of spaces to remove. If ``None``, defaults to the number 186 | of spaces of the first non-empty line. 187 | """ 188 | # Get the nb of empty spaces to fill 189 | if n is None: 190 | first = self.first_non_empty(start) 191 | n = len(first) - len(first.lstrip()) 192 | # Get the target 193 | if end is None: 194 | target = slice(start, end) 195 | else: 196 | target = slice(start, end+1) 197 | # Now, get rid of the leading spaces in target (if possible) 198 | for (i,line) in enumerate(self[target]): 199 | if line[:n] == ' '*n: 200 | self[i+start] = line[n:] 201 | return self 202 | # 203 | def indent(self, n=0, start=0, end=None): 204 | """ 205 | Adds `n` spaces at the beginning of each line, **in place**. 206 | 207 | :Parameters: 208 | **n** : int, optional 209 | Number of space to add. 210 | **start** : int, optional 211 | Index of the first line to process 212 | **end** : {None, int}, optional 213 | Index of the last line to process 214 | """ 215 | if n: 216 | if end is None: 217 | target = slice(start, end) 218 | else: 219 | target = slice(start, end+1) 220 | self[target] = [' '*n + line for line in self[target]] 221 | return self 222 | # 223 | def is_at_section(self, idx): 224 | """ 225 | Returns True if a section starts at line `idx`. 226 | 227 | """ 228 | try: 229 | current_line = self[idx] 230 | except IndexError: 231 | return False 232 | # 233 | if not current_line.strip() or (idx == len(self)-1): 234 | return False 235 | # 236 | next_line = self[idx+1] 237 | if not next_line.strip(): 238 | return False 239 | elif (len(next_line) == len(current_line)) and\ 240 | set(next_line).issubset('"-_~`'): 241 | return True 242 | return False 243 | # 244 | def is_at_directive(self, idx): 245 | """ 246 | Returns True if a directive is detected at line `idx`. 247 | 248 | """ 249 | # 250 | try: 251 | match = directive_rgx.match(self[idx]) 252 | except IndexError: 253 | return 254 | else: 255 | if match and match.group('desc') not in ['math']: 256 | return match 257 | return 258 | # 259 | def get_indented(self, start=0, until_blank=0, strip_indent=1, 260 | block_indent=None, first_indent=None): 261 | """ 262 | Extract and return a Docstring of indented lines of text. 263 | 264 | Collect all lines with indentation, determine the minimum indentation, 265 | remove the minimum indentation from all indented lines (unless `strip_indent` 266 | is false), and return them. All lines up to but not including the first 267 | unindented line will be returned. 268 | 269 | :Parameters: 270 | - `start`: The index of the first line to examine. 271 | - `until_blank`: Stop collecting at the first blank line if true. 272 | - `strip_indent`: Strip common leading indent if true (default). 273 | - `block_indent`: The indent of the entire block, if known. 274 | - `first_indent`: The indent of the first line, if known. 275 | 276 | :Return: 277 | - a StringList of indented lines with mininum indent removed; 278 | - the amount of the indent; 279 | - a boolean: did the indented block finish with a blank line or EOF? 280 | 281 | .. note:: 282 | 283 | This method is nothing but ``docutils.statemachine.StringList.get_indented`` 284 | """ 285 | indent = block_indent # start with None if unknown 286 | end = start 287 | if block_indent is not None and first_indent is None: 288 | first_indent = block_indent 289 | if first_indent is not None: 290 | end += 1 291 | last = len(self) 292 | while end < last: 293 | line = self[end] 294 | if line and (line[0] != ' ' 295 | or (block_indent is not None 296 | and line[:block_indent].strip())): 297 | # Line not indented or insufficiently indented. 298 | # Block finished properly iff the last indented line blank: 299 | blank_finish = ((end > start) and not self[end-1].strip()) 300 | break 301 | stripped = line.lstrip() 302 | if not stripped: # blank line 303 | if until_blank: 304 | blank_finish = 1 305 | break 306 | elif block_indent is None: 307 | line_indent = len(line) - len(stripped) 308 | if indent is None: 309 | indent = line_indent 310 | else: 311 | indent = min(indent, line_indent) 312 | end += 1 313 | else: 314 | blank_finish = 1 # block ends at end of lines 315 | block = self[start:end] 316 | if first_indent is not None and block: 317 | block[0] = block[0][first_indent:] 318 | if indent and strip_indent: 319 | block.deindent(indent, start=(first_indent is not None)) 320 | return (block, indent) or (0, blank_finish) 321 | 322 | 323 | def get_paragraphs(self): 324 | """Returns a list of contiguous blocks.""" 325 | blocks = [] 326 | i = 0 327 | while i < len(self): 328 | j = i 329 | current_block = [] 330 | line = self[j] 331 | while line and (j < len(self)-1): 332 | current_block += [line] 333 | j += 1 334 | line = self[j] 335 | blocks.append(current_block) 336 | i = j+1 337 | return blocks 338 | 339 | def paragraphs_as_list(self): 340 | """Transforms a list of paragraphs as a rst list.""" 341 | # 342 | blocks = self.get_paragraphs() 343 | lines = [] 344 | for block in blocks: 345 | if not block: 346 | lines.append('') 347 | else: 348 | lines.append('* ' + block[0]) 349 | lines += [' ' + line for line in block[1:]] 350 | self[:] = lines[:] 351 | return self 352 | 353 | #------------------------------------------------------------------------------ 354 | 355 | 356 | class NumpyDocString(Docstring): 357 | """ 358 | 359 | """ 360 | # 361 | name_format = default_name_format 362 | type_format = default_type_format 363 | default_block_format = default_block_format 364 | default_list_format = default_list_format 365 | # 366 | def __init__(self, docstring, 367 | block_format=None, list_format=None, role=''): 368 | # Remove the indentation, starting on the second line 369 | Docstring.__init__(self, docstring) 370 | self.deindent() 371 | self.section = {'Signature': [], 372 | 'Summary': '', 373 | 'Extended Summary': [], 374 | 'Parameters': [], 375 | 'Returns': [], 376 | 'Raises': [], 377 | 'Warns': [], 378 | 'Warnings': [], 379 | 'Other Parameters': [], 380 | 'Attributes': [], 381 | 'Methods': [], 382 | 'See Also': [], 383 | 'Notes': [], 384 | 'References': '', 385 | 'Examples': '', 386 | 'index': {} 387 | } 388 | # 389 | self._parsed = False 390 | self.section_slices = None 391 | # 392 | if block_format is None: 393 | block_format = self.default_block_format 394 | if list_format is None: 395 | list_format = self.default_list_format 396 | self.block_formatter = BlockFormatter(block_format) 397 | self.list_formatter = BlockFormatter(list_format) 398 | self._role = role 399 | 400 | 401 | def __getitem__(self, item): 402 | if isinstance(item, basestring): 403 | return self.section[item] 404 | #print "NUmpyDocString[%s]:%s" % (item, '\n'.join(self)) 405 | try: 406 | return Docstring.__getitem__(self, item) 407 | except IndexError: 408 | return '' 409 | 410 | def __setitem__(self, item, value): 411 | if isinstance(item, basestring): 412 | self.section[item] = value 413 | else: 414 | Docstring.__setitem__(self, item, value) 415 | 416 | def __delitem__(self, item): 417 | if isinstance(item, basestring): 418 | del self.section[item] 419 | else: 420 | Docstring.__delitem__(self, item) 421 | 422 | 423 | def extract_signature(self): 424 | """ 425 | If the first line of the docstring is a signature followed by an empty line, 426 | remove the first two lines from the docstring and return the argument list. 427 | If no signature is found, return the docstring as is and None as the 428 | argument list. 429 | 430 | :param docstring: A StringList object containing the docstring. 431 | :return: (docstring, argument list) 432 | """ 433 | sig = pysig_rgx.match(self[0].strip()) 434 | empty = (len(self) == 1) or (self[1].strip() == '') 435 | 436 | # Now check if the arguments are valid. 437 | if sig and empty: 438 | # Get the arguments 439 | args = sig.group('args') 440 | # Remove the spaces 441 | arglist = [a.strip() for a in args.split(',')] 442 | # Validate the regex 443 | valid_args = [a for a in arglist if arguments_rgx.match(a)] 444 | # Print valid_args 445 | if arglist == [''] or (arglist == valid_args): 446 | self['Signature'] = sig.group() 447 | del self[:2] 448 | return "(%s)" % ', '.join(arglist) 449 | return '' 450 | 451 | 452 | def parse(self): 453 | # 454 | self.extract_signature() 455 | self.deindent() 456 | # 457 | sectidx = {} 458 | sectidx['Summary'] = contentidx = [0, 0] 459 | i = 0 460 | while (i < len(self)): 461 | if self.is_at_section(i): 462 | key = self[i].lower() 463 | try: 464 | header = synonyms[key] 465 | except KeyError: 466 | warn("Unknown section %s" % key) 467 | i += 2 468 | sectidx[header] = contentidx = [i, i] 469 | continue 470 | match = self.is_at_directive(i) 471 | if match: 472 | header = synonyms[match.group('desc').lower()] 473 | args = match.group('arg') 474 | self.insert(i+1, args or ' ') 475 | sectidx[header] = contentidx = [i+1, i+1] 476 | else: 477 | contentidx[-1] += 1 478 | i += 1 479 | # 480 | self.section_slices = dict([(h, slice(s[0],s[1])) 481 | for (h,s) in sectidx.iteritems()]) 482 | self.section.update([(h, self[s[0]:s[1]]) 483 | for (h,s) in sectidx.iteritems()]) 484 | # 485 | for header in ['Parameters', 'Returns', 'Raises', 'Warns']: 486 | self._fix_description(header) 487 | self._fix_examples() 488 | self._fix_see_also() 489 | self._fix_notes() 490 | self._fix_references() 491 | self._parsed = True 492 | return self 493 | 494 | 495 | def _split_description(self, header): 496 | # 497 | if (header not in self.section) or (not self.section[header]): 498 | return [] 499 | # 500 | contents = self[header] 501 | output = [] 502 | i = 0 503 | while i < len(contents): 504 | line = contents[i] 505 | match = description_rgx.match(line.rstrip()) 506 | if match: 507 | (v_name, v_type) = match.groups() 508 | (desc, _) = contents.get_indented(start=i+1, until_blank=True) 509 | output.append((v_name, v_type, desc)) 510 | i += len(desc) 511 | else: 512 | i+=1 513 | return output 514 | 515 | 516 | def _fix_description(self, header): 517 | # 518 | if (header not in self.section) or (not self.section[header]): 519 | return [] 520 | # 521 | contents = self.section[header] 522 | output = [] 523 | i = 0 524 | while i < len(contents): 525 | line = contents[i] 526 | match = description_rgx.match(line.rstrip()) 527 | if match: 528 | (v_name, v_type) = match.groups() 529 | output.append('') 530 | if v_type: 531 | output.append("%s : %s" % (self.name_format % v_name, 532 | self.type_format % v_type)) 533 | else: 534 | output.append("%s" % (self.name_format % v_name)) 535 | (desc, _) = contents.get_indented(start=i+1, 536 | until_blank=True) 537 | output += desc.indent(3) 538 | # If desc == [] we still need to increment. 539 | i += len(desc) or 1 540 | else: 541 | i +=1 542 | output.append('') 543 | if output != ['']: 544 | contents[:] = output 545 | return output 546 | 547 | 548 | def _fix_examples(self): 549 | # 550 | def opened_quote(text): 551 | odd_single = (len(text.split("'")) % 2) 552 | odd_double = (len(text.split('"')) % 2) 553 | return not (odd_single and odd_double) 554 | # 555 | if not self['Examples']: 556 | return [] 557 | contents = self['Examples'] 558 | contents.insert(0,'') 559 | (i, ilast) = (0, len(contents)) 560 | while i < ilast: 561 | line = contents[i] 562 | if doctest_rgx.match(line) and opened_quote(line): 563 | j = i+1 564 | while j < ilast: 565 | next_line = contents[j] 566 | if next_line == '': 567 | contents[i] += "\\n" 568 | ilast -= 1 569 | del contents[j] 570 | elif opened_quote(next_line): 571 | contents[i] += "\\n" + next_line 572 | ilast -= 1 573 | del contents[j] 574 | break 575 | else: 576 | j += 1 577 | i += 1 578 | return contents 579 | 580 | 581 | def _fix_see_also(self): 582 | if not self.section['See Also']: 583 | return [] 584 | # 585 | func_role = self._role 586 | functions = [] 587 | current_func = None 588 | rest = Docstring([]) 589 | for line in self.section['See Also']: 590 | if not line.strip(): continue 591 | if ':' in line: 592 | if current_func: 593 | functions.append((current_func, rest)) 594 | r = line.split(':', 1) 595 | current_func = r[0].strip() 596 | r[1] = r[1].strip() 597 | if r[1]: 598 | rest = Docstring([r[1]]) 599 | else: 600 | rest = Docstring([]) 601 | elif not line.startswith(' '): 602 | if current_func: 603 | functions.append((current_func, rest)) 604 | current_func = None 605 | rest = Docstring([]) 606 | if ',' in line: 607 | for func in line.split(','): 608 | func = func.strip() 609 | if func: 610 | functions.append((func, [])) 611 | elif line.strip(): 612 | current_func = line.strip() 613 | elif current_func is not None: 614 | rest.append(line.strip()) 615 | if current_func: 616 | functions.append((current_func, rest)) 617 | # 618 | output = [] 619 | last_had_desc = True 620 | for (func, desc) in functions: 621 | if func_role: 622 | link = ":%s:`%s`" % (func_role, func) 623 | else: 624 | link = "`%s`_" % func 625 | if desc or last_had_desc: 626 | output += [''] 627 | output += [link] 628 | else: 629 | output[-1] += ", %s" % link 630 | if desc: 631 | output += desc.indent(4) 632 | last_had_desc = True 633 | else: 634 | last_had_desc = False 635 | output += [''] 636 | self['See Also'][:] = output 637 | return output 638 | 639 | 640 | def _fix_references(self): 641 | pass 642 | 643 | 644 | def _fix_notes(self): 645 | if self['Notes'] and self['Notes'][0] != '': 646 | self['Notes'].insert(0,'') 647 | 648 | 649 | def _split_index(self): 650 | """ 651 | .. index: default 652 | :refguide: something, else, and more 653 | 654 | """ 655 | def strip_each_in(lst): 656 | return [s.strip() for s in lst] 657 | # 658 | info = self['index'] 659 | if not info: 660 | return {} 661 | # 662 | (section, content) = (info[0], info[1:]) 663 | out = {} 664 | out['default'] = strip_each_in(section.strip().split(','))[0] 665 | for line in content: 666 | match = field_rgx.match(line) 667 | if match: 668 | (name, args) = match.groups() 669 | out[name] = strip_each_in((args or '').split(',')) 670 | return out 671 | 672 | def _format_block(self, header): 673 | if self[header]: 674 | return self.block_formatter(header, self[header]) 675 | return [] 676 | 677 | def _format_list(self, header): 678 | if self[header]: 679 | return self.list_formatter(header, self[header]) 680 | return [] 681 | 682 | def _format_signature(self): 683 | if self['Signature']: 684 | return [self['Signature'].replace('*','\*')] + [''] 685 | else: 686 | return [''] 687 | 688 | def _format_see_also(self): 689 | if not self['See Also']: 690 | return [] 691 | return self.block_formatter('See Also', self['See Also']) 692 | 693 | def _format_index(self): 694 | idx = self._split_index() 695 | out = [] 696 | out += ['.. index:: %s' % idx.get('default','')] 697 | for section, references in idx.iteritems(): 698 | if section == 'default': 699 | continue 700 | out += [' :%s: %s' % (section, ', '.join(references))] 701 | return out 702 | 703 | def _format_warnings(self): 704 | if self['Warnings']: 705 | return self._format_block('Warnings') 706 | return [] 707 | 708 | def format(self): 709 | if not self._parsed: 710 | self.parse() 711 | section = self.section 712 | out = Docstring([]) 713 | out += self._format_signature() 714 | # 715 | for header in ['Summary', 'Extended Summary']: 716 | if section[header]: 717 | out += section[header] 718 | if out[-1] != '': 719 | out += [''] 720 | # 721 | for header in ('Parameters','Returns','Raises', 'Warns'): 722 | out += self._format_list(header) 723 | # 724 | out += self._format_see_also() #("obj") 725 | out += self._format_block('Notes') 726 | out += self._format_warnings() 727 | out += self._format_block('References') 728 | out += self._format_block('Examples') 729 | # out = Docstring(out).indent(indent) 730 | return out 731 | 732 | def __str__(self): 733 | return '\n'.join(self.format()) 734 | 735 | 736 | class FunctionDoc(NumpyDocString): 737 | # 738 | default_block_format = default_block_format 739 | default_list_format = default_list_format 740 | # 741 | def __init__(self, func, role='func', 742 | block_format=None, list_format=None): 743 | self._f = func 744 | try: 745 | NumpyDocString.__init__(self, inspect.getdoc(func) or '', 746 | role=role, 747 | block_format=block_format, 748 | list_format=list_format) 749 | except ValueError, e: 750 | print '*'*78 751 | print "ERROR: '%s' while parsing `%s`" % (e, self._f) 752 | print '*'*78 753 | #print "Docstring follows:" 754 | #print doclines 755 | #print '='*78 756 | 757 | def _get_signature(self): 758 | try: 759 | # try to read signature 760 | argspec = inspect.getargspec(self._f) 761 | argspec = inspect.formatargspec(*argspec) 762 | argspec = argspec.replace('*','\*') 763 | signature = '%s%s' % (self._f.__name__, argspec) 764 | except TypeError: 765 | signature = '%s()' % self._f.__name__ 766 | self['Signature'] = signature 767 | return signature 768 | 769 | def format(self): 770 | if not self._parsed: 771 | self.parse() 772 | 773 | 774 | out = [] 775 | 776 | roles = {'func': 'function', 777 | 'meth': 'method'} 778 | if self._role: 779 | role = roles.get(self._role, '') 780 | if not role: 781 | print "Warning: invalid role %s" % self._role 782 | sig = self['Signature'] 783 | if not self['Signature']: 784 | self['Signature'] = self._get_signature() 785 | if sig is None: 786 | out += ['.. %s:: %s' % (role, self._f.__name__), ''] 787 | else: 788 | out += ['.. %s:: %s(%s)' % (role, self._f.__name__, sig), ''] 789 | 790 | out += super(FunctionDoc, self).format().indent(3) 791 | 792 | return out 793 | 794 | 795 | class ClassDoc(NumpyDocString): 796 | # 797 | default_block_format = default_block_format 798 | default_list_format = default_list_format 799 | # 800 | def __init__(self, cls, modulename='', func_doc=FunctionDoc, 801 | block_format=None, list_format=None, role=''): 802 | if not inspect.isclass(cls): 803 | raise ValueError("Initialise using a class. Got %r" % cls) 804 | self._cls = cls 805 | 806 | if modulename and not modulename.endswith('.'): 807 | modulename += '.' 808 | self._mod = modulename 809 | self._name = cls.__name__ 810 | self._func_doc = func_doc 811 | 812 | NumpyDocString.__init__(self, cls.__doc__, role=role, 813 | block_format=block_format, 814 | list_format=list_format) 815 | 816 | @property 817 | def methods(self): 818 | return [name for (name, func) in inspect.getmembers(self._cls) 819 | if not name.startswith('_') and callable(func)] 820 | 821 | def __str__(self): 822 | out = '' 823 | out += super(ClassDoc, self).__str__() 824 | out += "\n\n" 825 | return out 826 | 827 | 828 | ################################################################################ 829 | 830 | class SphinxDocString(NumpyDocString): 831 | 832 | default_block_format = default_block_format 833 | default_list_format = default_list_format 834 | 835 | def _fix_references(self): 836 | references = self['References'] 837 | for (i,line) in enumerate(references): 838 | references[i] = re.sub('\[[0-9]+\]', '[#]', line) 839 | if references: 840 | references.append('') 841 | return 842 | 843 | def _format_signature(self): 844 | if self['Signature']: 845 | return ['``%s``' % self['Signature']] + [''] 846 | return [''] 847 | 848 | def _format_warnings(self): 849 | out = [] 850 | if self['Warnings']: 851 | out = ['.. warning::'] 852 | out += self['Warnings'].indent(3) 853 | return out 854 | 855 | def _format_see_also(self): 856 | out = [] 857 | if self['See Also']: 858 | out = ['.. seealso::'] 859 | out += self['See Also'].indent(3) 860 | return out 861 | 862 | def _format_index(self): 863 | idx = self._split_index() 864 | if not idx: 865 | return [] 866 | 867 | out = [] 868 | out += ['.. index:: %s' % idx.get('default','')] 869 | for section, references in idx.iteritems(): 870 | if section == 'default': 871 | continue 872 | elif section == 'refguide': 873 | out += [' single: %s' % (', '.join(references))] 874 | else: 875 | out += [' %s: %s' % (section, ','.join(references))] 876 | return out 877 | 878 | 879 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 880 | default_block_format = default_block_format 881 | default_list_format = default_list_format 882 | 883 | 884 | class SphinxClassDoc(SphinxDocString, ClassDoc): 885 | default_block_format = default_block_format 886 | default_list_format = default_list_format 887 | pass 888 | 889 | ################################################################################ 890 | 891 | if __name__ == '__main__': 892 | basetext = """ 893 | A one-line summary that does not use variable names or the 894 | function name. 895 | 896 | Several sentences providing an extended description. Refer to 897 | variables using back-ticks, e.g. `var`. 898 | 899 | Parameters 900 | ---------- 901 | var1 : array_like 902 | Array_like means all those objects -- lists, nested lists, etc. -- 903 | that can be converted to an array. We can also refer to 904 | variables like `var1`. 905 | var2 : int 906 | The type above can either refer to an actual Python type 907 | (e.g. ``int``), or describe the type of the variable in more 908 | detail, e.g. ``(N,) ndarray`` or ``array_like``. 909 | Long_variable_name : {'hi', 'ho'}, optional 910 | Choices in brackets, default first when optional. 911 | 912 | Returns 913 | ------- 914 | describe : type 915 | Explanation 916 | output 917 | Explanation 918 | tuple 919 | Explanation 920 | items 921 | even more explaining 922 | 923 | Other Parameters 924 | ---------------- 925 | only_seldom_used_keywords : type 926 | Explanation 927 | common_parameters_listed_above : type 928 | Explanation 929 | 930 | Raises 931 | ------ 932 | BadException 933 | Because you shouldn't have done that. 934 | 935 | See Also 936 | -------- 937 | otherfunc : relationship (optional) 938 | newfunc : Relationship (optional), which could be fairly long, in which 939 | case the line wraps here. 940 | thirdfunc, fourthfunc, fifthfunc 941 | 942 | Notes 943 | ----- 944 | Notes about the implementation algorithm (if needed). 945 | 946 | This can have multiple paragraphs. 947 | 948 | You may include some math: 949 | 950 | .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} 951 | 952 | And even use a greek symbol like :math:`omega` inline. 953 | 954 | References 955 | ---------- 956 | Cite the relevant literature, e.g. [1]_. You may also cite these 957 | references in the notes section above. 958 | 959 | .. [1] O. McNoleg, "The integration of GIS, remote sensing, 960 | expert systems and adaptive co-kriging for environmental habitat 961 | modelling of the Highland Haggis using object-oriented, fuzzy-logic 962 | and neural-network techniques," Computers & Geosciences, vol. 22, 963 | pp. 585-588, 1996. 964 | 965 | Examples 966 | -------- 967 | These are written in doctest format, and should illustrate how to 968 | use the function. 969 | 970 | >>> a=[1,2,3] 971 | >>> print [x + 3 for x in a] 972 | [4, 5, 6] 973 | >>> print "a\n\nb" 974 | a 975 | 976 | b 977 | """ 978 | doc = FunctionDoc(basetext) -------------------------------------------------------------------------------- /docs/ext/numpydoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Numpydoc : adds autodirectives to process docstrings with the Numpy format. 4 | 5 | This module is basically Puali Virtanen's with a few modifications: 6 | 7 | * The base classes SphinxDocString, SphinxClassDoc and SphinxFunctionDoc 8 | are PGM's versions. 9 | * If a signature was detected in the docstring, it is removed. 10 | * The format_signature function is now a method of autodoc.RstGenerator, 11 | (as of Sphinx-0.4dev-20080724) 12 | * As setup calls autodoc.setup, there's no need for registering 13 | sphinx.ext.autodoc in the conf.py file. 14 | 15 | 16 | 17 | """ 18 | 19 | 20 | import inspect 21 | import os 22 | import re 23 | 24 | from docutils.parsers.rst import directives 25 | from docutils.statemachine import StringList 26 | 27 | from sphinx.directives import desc_directive 28 | import sphinx.ext.autodoc as autodoc 29 | 30 | from docscraper import NumpyDocString, SphinxDocString, SphinxClassDoc,\ 31 | SphinxFunctionDoc 32 | 33 | #------------------------------------------------------------------------------- 34 | #--- .. numpydirectives:: 35 | #------------------------------------------------------------------------------- 36 | 37 | def numpyfunction_directive(desctype, arguments, options, content, lineno, 38 | content_offset, block_text, state, state_machine): 39 | 40 | try: 41 | doc = SphinxDocString(content, block_format=None, list_format=None) 42 | sig = doc.extract_signature() 43 | text = StringList(doc.format()) 44 | 45 | if sig is not None: 46 | arguments[0] += sig 47 | interpreted = desc_directive('function', arguments, options, text, 48 | lineno, content_offset, text, state, 49 | state_machine) 50 | return interpreted 51 | except: 52 | raise 53 | # exc_info = sys.exc_info() 54 | # literal_block = nodes.literal_block(block_text, block_text) 55 | # return [state_machine.reporter.error('error: %s' % str(exc_info[:2]), 56 | # literal_block, 57 | # line=lineno)] 58 | 59 | 60 | def numpyclass_directive(desctype, arguments, options, content, lineno, 61 | content_offset, block_text, state, state_machine): 62 | 63 | try: 64 | doc = SphinxDocString(content) 65 | sig = doc.extract_signature() 66 | text = StringList(doc.format()) 67 | if sig is not None: 68 | arguments[0] += sig 69 | interpreted = desc_directive('class', arguments, options, text, 70 | lineno, content_offset, text, state, 71 | state_machine) 72 | return interpreted 73 | except: 74 | raise 75 | # exc_info = sys.exc_info() 76 | # literal_block = nodes.literal_block(block_text, block_text) 77 | # return [state_machine.reporter.error('error: %s' % str(exc_info[:2]), 78 | # literal_block, 79 | # line=lineno)] 80 | 81 | 82 | #------------------------------------------------------------------------------ 83 | #--- .. autosummary:: 84 | #------------------------------------------------------------------------------ 85 | from docutils.statemachine import ViewList 86 | from docutils import nodes 87 | from sphinx import addnodes 88 | from sphinx.util import patfilter 89 | import posixpath 90 | 91 | def autosummary_directive(dirname, arguments, options, content, lineno, 92 | content_offset, block_text, state, state_machine): 93 | names = [] 94 | names += [x for x in content if x.strip()] 95 | 96 | result, warnings = get_autosummary(names, state.document) 97 | 98 | node = nodes.paragraph() 99 | state.nested_parse(result, 0, node) 100 | 101 | env = state.document.settings.env 102 | suffix = env.config.source_suffix 103 | all_docnames = env.found_docs.copy() 104 | dirname = posixpath.dirname(env.docname) 105 | 106 | docnames = [] 107 | doctitles = {} 108 | for docname in names: 109 | docname = 'generated/' + docname 110 | doctitles[docname] = '' 111 | if docname.endswith(suffix): 112 | docname = docname[:-len(suffix)] 113 | docname = posixpath.normpath(posixpath.join(dirname, docname)) 114 | if docname not in env.found_docs: 115 | warnings.append(state.document.reporter.warning( 116 | 'toctree references unknown document %r' % docname, 117 | line=lineno)) 118 | docnames.append(docname) 119 | 120 | tocnode = addnodes.toctree() 121 | tocnode['includefiles'] = docnames 122 | tocnode['includetitles'] = doctitles 123 | tocnode['maxdepth'] = -1 124 | tocnode['glob'] = None 125 | 126 | return warnings + node.children + [tocnode] 127 | 128 | 129 | 130 | def get_autosummary(names, document): 131 | result = ViewList() 132 | warnings = [] 133 | 134 | prefixes = [''] 135 | prefixes.append(document.settings.env.currmodule) 136 | 137 | max_name_len = max(map(len, names)) + 7 138 | link_fmt = '%%-%ds' % max_name_len 139 | 140 | table_banner = ('='*max_name_len) + ' ' + '===============' 141 | result.append(table_banner, '') 142 | 143 | for name in names: 144 | try: 145 | obj = import_by_name(name, prefixes=prefixes) 146 | doclines = (obj.__doc__ or '').split("\n") 147 | except ImportError: 148 | warnings.append(document.reporter.warning( 149 | 'failed to import %s' % name)) 150 | continue 151 | 152 | while doclines and (not doclines[0].strip() 153 | or re.search(r'[\w.]\(.*\)', doclines[0])): 154 | doclines = doclines[1:] # skip possible signature or empty lines 155 | 156 | if doclines: 157 | result.append((link_fmt + " %s") % (":obj:`%s`" % name, 158 | doclines[0].strip()), 159 | '') 160 | else: 161 | result.append(link_fmt % name, '') 162 | result.append(table_banner, '') 163 | result.append('', '') 164 | 165 | return result, warnings 166 | 167 | def import_by_name(name, prefixes=None): 168 | for prefix in prefixes or ['']: 169 | try: 170 | if prefix: 171 | prefixed_name = '.'.join([prefix, name]) 172 | else: 173 | prefixed_name = name 174 | return _import_by_name(prefixed_name) 175 | except ImportError: 176 | pass 177 | raise ImportError 178 | 179 | 180 | def _import_by_name(name): 181 | try: 182 | name_parts = name.split('.') 183 | last_j = 0 184 | modname = None 185 | for j in reversed(range(1, len(name_parts)+1)): 186 | last_j = j 187 | modname = '.'.join(name_parts[:j]) 188 | try: 189 | __import__(modname) 190 | except ImportError: 191 | continue 192 | if modname in sys.modules: 193 | break 194 | 195 | if last_j < len(name_parts): 196 | obj = sys.modules[modname] 197 | for obj_name in name_parts[last_j:]: 198 | obj = getattr(obj, obj_name) 199 | return obj 200 | else: 201 | return sys.modules[modname] 202 | except (ValueError, ImportError, AttributeError, KeyError), e: 203 | raise ImportError(e) 204 | 205 | 206 | #------------------------------------------------------------------------------ 207 | #--- Creating 'phantom' modules from an XML description 208 | #------------------------------------------------------------------------------ 209 | import imp, sys, compiler, types 210 | 211 | def import_phantom_module(xml_file): 212 | import lxml.etree as etree 213 | 214 | object_cache = {} 215 | 216 | tree = etree.parse(xml_file) 217 | root = tree.getroot() 218 | 219 | dotsort = lambda x: x.attrib['id'].count('.') 220 | 221 | # Create phantom items 222 | for node in sorted(root, key=dotsort): 223 | name = node.attrib['id'] 224 | doc = (node.text or '').decode('string-escape') + "\n" 225 | 226 | # create parent, if missing 227 | parent = name 228 | while True: 229 | parent = '.'.join(parent.split('.')[:-1]) 230 | if not parent: break 231 | if parent in object_cache: break 232 | obj = imp.new_module(parent) 233 | object_cache[parent] = obj 234 | sys.modules[parent] = obj 235 | 236 | # create object 237 | if node.tag == 'module': 238 | obj = imp.new_module(name) 239 | obj.__doc__ = doc 240 | sys.modules[name] = obj 241 | elif node.tag == 'class': 242 | obj = type(name, (), {'__doc__': doc}) 243 | elif node.tag == 'callable': 244 | funcname = node.attrib['id'].split('.')[-1] 245 | argspec = node.attrib.get('argspec') 246 | if argspec: 247 | argspec = re.sub('^[^(]*', '', argspec) 248 | doc = "%s%s\n\n%s" % (funcname, argspec, doc) 249 | obj = lambda: 0 250 | obj.__argspec_is_invalid_ = True 251 | obj.func_name = funcname 252 | obj.__name__ = name 253 | obj.__doc__ = doc 254 | else: 255 | class Dummy(object): pass 256 | obj = Dummy() 257 | obj.__name__ = name 258 | obj.__doc__ = doc 259 | if inspect.isclass(object_cache[parent]): 260 | obj.__get__ = lambda: None 261 | object_cache[name] = obj 262 | 263 | if parent: 264 | obj.__module__ = parent 265 | setattr(object_cache[parent], name.split('.')[-1], obj) 266 | 267 | # Populate items 268 | for node in root: 269 | obj = object_cache.get(node.attrib['id']) 270 | if obj is None: continue 271 | for ref in node.findall('ref'): 272 | setattr(obj, ref.attrib['name'], 273 | object_cache.get(ref.attrib['ref'])) 274 | 275 | 276 | #------------------------------------------------------------------------------ 277 | #--- Manglers 278 | #------------------------------------------------------------------------------ 279 | 280 | def mangle_docstrings(app, what, name, obj, options, lines, 281 | reference_offset=[0]): 282 | cfg = app.config 283 | if what == 'class': 284 | doc = SphinxClassDoc(obj, role='', #func_doc=SphinxFunctionDoc, 285 | block_format=cfg.numpydoc_default_block_type, 286 | list_format=cfg.numpydoc_default_list_type) 287 | lines[:] = doc.format() 288 | elif what in ('function', 'method'): 289 | doc = SphinxFunctionDoc(obj, role='', 290 | block_format=cfg.numpydoc_default_block_type, 291 | list_format=cfg.numpydoc_default_list_type) 292 | docstring = doc.parse() 293 | docstring['Signature'] = [] 294 | lines[:] = docstring.format() 295 | elif what == 'module': 296 | pass 297 | else: 298 | doc = SphinxDocString(obj.__doc__, 299 | block_format=cfg.numpydoc_default_block_type, 300 | list_format=cfg.numpydoc_default_list_type) 301 | lines[:] = doc.format() 302 | 303 | if app.config.numpydoc_edit_link and getattr(obj, '__name__', ''): 304 | v = dict(full_name=obj.__name__) 305 | lines += [''] + (app.config.numpydoc_edit_link % v).split('\n') 306 | # replace reference numbers so that there are no duplicates 307 | references = [] 308 | for l in lines: 309 | l = l.strip() 310 | if l.startswith('.. ['): 311 | try: 312 | references.append(int(l[len('.. ['):l.index(']')])) 313 | except ValueError: 314 | print "WARNING: invalid reference in %s docstring" % name 315 | # Start renaming from the biggest number, otherwise we may 316 | # overwrite references. 317 | references.sort() 318 | if references: 319 | for i, line in enumerate(lines): 320 | for r in references: 321 | new_r = reference_offset[0] + r 322 | lines[i] = lines[i].replace('[%d]_' % r, 323 | '[%d]_' % new_r) 324 | lines[i] = lines[i].replace('.. [%d]' % r, 325 | '.. [%d]' % new_r) 326 | reference_offset[0] += len(references) 327 | return 328 | 329 | def mangle_signature(app, what, name, obj, options, args, retann): 330 | if args is not None: 331 | return (args, '') 332 | # 333 | # Do not try to inspect classes that don't define `__init__` 334 | if inspect.isclass(obj) and obj.__init__.__doc__ and \ 335 | ('initializes x; see ' in obj.__init__.__doc__): 336 | return ('', '') 337 | # 338 | if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): 339 | return (None, None) 340 | # 341 | obj_doc = getattr(obj, '__doc__', None) 342 | if not obj_doc: 343 | return (None, None) 344 | # 345 | args = NumpyDocString(obj_doc).extract_signature() 346 | if args is None: 347 | if inspect.isclass(obj): 348 | args = SphinxClassDoc(obj)._get_signature() 349 | elif inspect.isfunction(obj) or inspect.ismethod(obj): 350 | args = SphinxFunctionDoc(obj)._get_signature() 351 | return (args, '') 352 | 353 | 354 | #------------------------------------------------------------------------------ 355 | #--- Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (old Sphinxes) 356 | #------------------------------------------------------------------------------ 357 | 358 | def monkeypatch_sphinx_ext_autodoc(): 359 | global _original_format_signature 360 | # 361 | if autodoc.RstGenerator.format_signature is our_format_signature: 362 | return 363 | # 364 | print "[numpydoc] Monkeypatching sphinx.ext.autodoc ..." 365 | _original_format_signature = autodoc.RstGenerator.format_signature 366 | autodoc.RstGenerator.format_signature = our_format_signature 367 | 368 | def our_format_signature(self, what, obj, args, retann): 369 | (args, retann) = mangle_signature(None, what, None, obj, None, None, None) 370 | if args is not None: 371 | return args 372 | else: 373 | return _original_format_signature(self, what, obj, args, retann) 374 | 375 | 376 | def initialize(app): 377 | try: 378 | app.connect('autodoc-process-signature', mangle_signature) 379 | except: 380 | monkeypatch_sphinx_ext_autodoc() 381 | 382 | fn = app.config.numpydoc_phantom_import_file 383 | if (fn and os.path.isfile(fn)): 384 | print "[numpydoc] Phantom importing modules from", fn, "..." 385 | import_phantom_module(fn) 386 | 387 | def setup(app): 388 | # 389 | autodoc.setup(app) 390 | # Directives options .................................. 391 | fnc_options = {'module':directives.unchanged, 392 | 'noindex': directives.flag,} 393 | mod_options = {'members': autodoc.members_option, 394 | 'undoc-members': directives.flag, 395 | 'noindex': directives.flag, 396 | 'platform': lambda x: x, 397 | 'synopsis': lambda x: x, 398 | 'deprecated': directives.flag} 399 | cls_options = {'members': autodoc.members_option, 400 | 'undoc-members': directives.flag, 401 | 'noindex': directives.flag, 402 | 'inherited-members': directives.flag, 403 | 'show-inheritance': directives.flag} 404 | # Register basic directives ........................... 405 | app.add_directive('numpyfunction', 406 | numpyfunction_directive, 407 | 1, (1,0,1), **fnc_options) 408 | app.add_directive('numpyclass', 409 | numpyclass_directive, 410 | 1, (1,0,1), **cls_options) 411 | app.add_directive('numpymethod', 412 | numpyfunction_directive, 413 | 1, (1,0,1), **fnc_options) 414 | app.add_directive('numpystaticmethod', 415 | numpyfunction_directive, 416 | 1, (1,0,1), **fnc_options) 417 | app.add_directive('numpyexception', 418 | numpyfunction_directive, 419 | 1, (1,0,1), **fnc_options) 420 | # 421 | app.connect('autodoc-process-docstring', mangle_docstrings) 422 | app.connect('builder-inited', initialize) 423 | app.add_config_value('numpydoc_phantom_import_file', None, True) 424 | app.add_config_value('numpydoc_edit_link', None, True) 425 | app.add_config_value('numpydoc_default_block_type', None, True) 426 | app.add_config_value('numpydoc_default_list_type', "field", True) 427 | 428 | app.add_directive('autosummary', autosummary_directive, 1, (0, 0, False)) 429 | -------------------------------------------------------------------------------- /docs/ext/only_directives.py: -------------------------------------------------------------------------------- 1 | # 2 | # A pair of directives for inserting content that will only appear in 3 | # either html or latex. 4 | # 5 | 6 | from docutils.nodes import Body, Element 7 | from docutils.writers.html4css1 import HTMLTranslator 8 | from sphinx.latexwriter import LaTeXTranslator 9 | from docutils.parsers.rst import directives 10 | 11 | class html_only(Body, Element): 12 | pass 13 | 14 | class latex_only(Body, Element): 15 | pass 16 | 17 | def run(content, node_class, state, content_offset): 18 | text = '\n'.join(content) 19 | node = node_class(text) 20 | state.nested_parse(content, content_offset, node) 21 | return [node] 22 | 23 | try: 24 | from docutils.parsers.rst import Directive 25 | except ImportError: 26 | from docutils.parsers.rst.directives import _directives 27 | 28 | def html_only_directive(name, arguments, options, content, lineno, 29 | content_offset, block_text, state, state_machine): 30 | return run(content, html_only, state, content_offset) 31 | 32 | def latex_only_directive(name, arguments, options, content, lineno, 33 | content_offset, block_text, state, state_machine): 34 | return run(content, latex_only, state, content_offset) 35 | 36 | for func in (html_only_directive, latex_only_directive): 37 | func.content = 1 38 | func.options = {} 39 | func.arguments = None 40 | 41 | _directives['htmlonly'] = html_only_directive 42 | _directives['latexonly'] = latex_only_directive 43 | else: 44 | class OnlyDirective(Directive): 45 | has_content = True 46 | required_arguments = 0 47 | optional_arguments = 0 48 | final_argument_whitespace = True 49 | option_spec = {} 50 | 51 | def run(self): 52 | self.assert_has_content() 53 | return run(self.content, self.node_class, 54 | self.state, self.content_offset) 55 | 56 | class HtmlOnlyDirective(OnlyDirective): 57 | node_class = html_only 58 | 59 | class LatexOnlyDirective(OnlyDirective): 60 | node_class = latex_only 61 | 62 | directives.register_directive('htmlonly', HtmlOnlyDirective) 63 | directives.register_directive('latexonly', LatexOnlyDirective) 64 | 65 | def setup(app): 66 | app.add_node(html_only) 67 | app.add_node(latex_only) 68 | 69 | # Add visit/depart methods to HTML-Translator: 70 | def visit_perform(self, node): 71 | pass 72 | def depart_perform(self, node): 73 | pass 74 | def visit_ignore(self, node): 75 | node.children = [] 76 | def depart_ignore(self, node): 77 | node.children = [] 78 | 79 | HTMLTranslator.visit_html_only = visit_perform 80 | HTMLTranslator.depart_html_only = depart_perform 81 | HTMLTranslator.visit_latex_only = visit_ignore 82 | HTMLTranslator.depart_latex_only = depart_ignore 83 | 84 | LaTeXTranslator.visit_html_only = visit_ignore 85 | LaTeXTranslator.depart_html_only = depart_ignore 86 | LaTeXTranslator.visit_latex_only = visit_perform 87 | LaTeXTranslator.depart_latex_only = depart_perform 88 | -------------------------------------------------------------------------------- /docs/src/.static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cournape/talkbox/ee0ec30a6a6d483eb9284f72bdaf26bd99765f80/docs/src/.static/.gitignore -------------------------------------------------------------------------------- /docs/src/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # talkbox documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Sep 15 13:37:08 2008. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # The contents of this file are pickled, so don't put values in the namespace 9 | # that aren't pickleable (module imports are okay, they're removed automatically). 10 | # 11 | # All configuration values have a default value; values that are commented out 12 | # serve to show the default value. 13 | 14 | import sys, os 15 | from os.path import join as pjoin, dirname as pdirname 16 | 17 | # If your extensions are in another directory, add it here. If the directory 18 | # is relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | sys.path.extend([ 21 | os.path.abspath(os.path.dirname(__file__)), 22 | # numpy standard doc extensions 23 | os.path.join(os.path.dirname(__file__), '..', 'sphinxext')]) 24 | 25 | import sphinx 26 | # Check Sphinx version 27 | if sphinx.__version__ < "0.5": 28 | raise RuntimeError("Sphinx 0.5.dev or newer required") 29 | 30 | import talkbox_version 31 | 32 | # General configuration 33 | # --------------------- 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be extensions 36 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 37 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 38 | 'sphinx.ext.intersphinx', 'numpydoc', 'only_directives', 39 | 'sphinx.ext.pngmath.'] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['.templates'] 43 | 44 | # The suffix of source filenames. 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General substitutions. 51 | project = 'talkbox' 52 | copyright = '2008, David Cournapeau' 53 | 54 | # The default replacements for |version| and |release|, also used in various 55 | # other places throughout the built documents. 56 | # 57 | # The short X.Y version. 58 | version = talkbox_version.short_version 59 | # The full version, including alpha/beta/rc tags. 60 | release = talkbox_version.version 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | today_fmt = '%B %d, %Y' 67 | 68 | # List of documents that shouldn't be included in the build. 69 | #unused_docs = [] 70 | 71 | # List of directories, relative to source directories, that shouldn't be searched 72 | # for source files. 73 | #exclude_dirs = [] 74 | 75 | # The reST default role (used for this markup: `text`) to use for all documents. 76 | #default_role = None 77 | 78 | # If true, '()' will be appended to :func: etc. cross-reference text. 79 | #add_function_parentheses = True 80 | 81 | # If true, the current module name will be prepended to all description 82 | # unit titles (such as .. function::). 83 | #add_module_names = True 84 | 85 | # If true, sectionauthor and moduleauthor directives will be shown in the 86 | # output. They are ignored by default. 87 | #show_authors = False 88 | 89 | # The name of the Pygments (syntax highlighting) style to use. 90 | pygments_style = 'sphinx' 91 | 92 | 93 | # Options for HTML output 94 | # ----------------------- 95 | 96 | # The style sheet to use for HTML and HTML Help pages. A file of that name 97 | # must exist either in Sphinx' static/ path, or in one of the custom paths 98 | # given in html_static_path. 99 | html_style = 'default.css' 100 | 101 | # The name for this set of Sphinx documents. If None, it defaults to 102 | # " v documentation". 103 | #html_title = None 104 | 105 | # A shorter title for the navigation bar. Default is the same as html_title. 106 | #html_short_title = None 107 | 108 | # The name of an image file (within the static path) to place at the top of 109 | # the sidebar. 110 | #html_logo = None 111 | 112 | # The name of an image file (within the static path) to use as favicon of the 113 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 114 | # pixels large. 115 | #html_favicon = None 116 | 117 | # Add any paths that contain custom static files (such as style sheets) here, 118 | # relative to this directory. They are copied after the builtin static files, 119 | # so a file named "default.css" will overwrite the builtin "default.css". 120 | html_static_path = ['.static'] 121 | 122 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 123 | # using the given strftime format. 124 | html_last_updated_fmt = '%b %d, %Y' 125 | 126 | # If true, SmartyPants will be used to convert quotes and dashes to 127 | # typographically correct entities. 128 | #html_use_smartypants = True 129 | 130 | # Custom sidebar templates, maps document names to template names. 131 | #html_sidebars = {} 132 | 133 | # Additional templates that should be rendered to pages, maps page names to 134 | # template names. 135 | #html_additional_pages = {} 136 | 137 | # If false, no module index is generated. 138 | #html_use_modindex = True 139 | 140 | # If false, no index is generated. 141 | #html_use_index = True 142 | 143 | # If true, the index is split into individual pages for each letter. 144 | #html_split_index = False 145 | 146 | # If true, the reST sources are included in the HTML build as _sources/. 147 | #html_copy_source = True 148 | 149 | # If true, an OpenSearch description file will be output, and all pages will 150 | # contain a tag referring to it. The value of this option must be the 151 | # base URL from which the finished HTML is served. 152 | #html_use_opensearch = '' 153 | 154 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 155 | #html_file_suffix = '' 156 | 157 | # Output file base name for HTML help builder. 158 | htmlhelp_basename = 'talkboxdoc' 159 | 160 | 161 | # Options for LaTeX output 162 | # ------------------------ 163 | 164 | # The paper size ('letter' or 'a4'). 165 | #latex_paper_size = 'letter' 166 | 167 | # The font size ('10pt', '11pt' or '12pt'). 168 | #latex_font_size = '10pt' 169 | 170 | # Grouping the document tree into LaTeX files. List of tuples 171 | # (source start file, target name, title, author, document class [howto/manual]). 172 | latex_documents = [ 173 | ('index', 'talkbox.tex', 'talkbox Documentation', 174 | 'David Cournapeau', 'manual'), 175 | ] 176 | 177 | # The name of an image file (relative to this directory) to place at the top of 178 | # the title page. 179 | #latex_logo = None 180 | 181 | # For "manual" documents, if this is true, then toplevel headings are parts, 182 | # not chapters. 183 | #latex_use_parts = False 184 | 185 | # Additional stuff for the LaTeX preamble. 186 | latex_preamble = r''' 187 | \usepackage{amsmath,amssymb} 188 | ''' 189 | 190 | # Documents to append as an appendix to all manuals. 191 | #latex_appendices = [] 192 | 193 | # If false, no module index is generated. 194 | #latex_use_modindex = True 195 | 196 | # png math 197 | #pngmath_dvipng_args = ['-z3'] 198 | pngmath_use_preview = True 199 | -------------------------------------------------------------------------------- /docs/src/examples/arspec_phoneme.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | from scikits.audiolab import wavread 5 | from scikits.samplerate import resample 6 | 7 | from scikits.talkbox.spectral.basic import periodogram, arspec 8 | 9 | a, fs = wavread('voice-womanKP-01.wav')[:2] 10 | 11 | fr = 4000. 12 | ra = resample(a, fr / fs) 13 | 14 | frame = ra[500:500+256] 15 | px, fx = periodogram(frame, 2048, fr) 16 | plt.grid(True) 17 | plt.plot(fx, 10 * np.log10(px)) 18 | 19 | apx, afx = arspec(frame, 12, 2048, fr) 20 | plt.plot(afx, 10 * np.log10(apx)) 21 | -------------------------------------------------------------------------------- /docs/src/examples/periodogram_1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scikits.talkbox.spectral.basic import periodogram 4 | fs = 1000 5 | x = np.sin(2 * np.pi * 0.15 * fs * np.linspace(0., 0.3, 0.3 * fs)) 6 | x += 0.1 * np.random.randn(x.size) 7 | px, fx = periodogram(x, nfft=16384, fs=fs) 8 | plt.plot(fx, 10 * np.log10(px)) 9 | plt.xlabel('Frequency (Hz)') 10 | plt.ylabel('Amplitude (dB)') 11 | plt.savefig('periodogram_1.png') 12 | -------------------------------------------------------------------------------- /docs/src/examples/periodogram_2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scikits.talkbox.spectral.basic import periodogram 4 | from scipy.signal import hamming, hanning 5 | fs = 1000 6 | x = np.sin(2 * np.pi * 0.15 * fs * np.linspace(0., 0.3, 0.3 * fs)) 7 | x += 0.1 * np.random.randn(x.size) 8 | px1, fx1 = periodogram(x, nfft=16384, fs=fs) 9 | px2, fx2 = periodogram(x * hamming(x.size), nfft=16384, fs=fs) 10 | plt.subplot(2, 1, 1) 11 | plt.plot(fx1, 10 * np.log10(px1)) 12 | plt.subplot(2, 1, 2) 13 | plt.plot(fx2, 10 * np.log10(px2)) 14 | plt.xlabel('Frequency (Hz)') 15 | plt.ylabel('Amplitude (dB)') 16 | plt.savefig('periodogram_2.png') 17 | -------------------------------------------------------------------------------- /docs/src/examples/voice-womanKP-01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cournape/talkbox/ee0ec30a6a6d483eb9284f72bdaf26bd99765f80/docs/src/examples/voice-womanKP-01.wav -------------------------------------------------------------------------------- /docs/src/examples/yop.m: -------------------------------------------------------------------------------- 1 | [a, fs] = wavread('voice-womanKP-01.wav'); 2 | 3 | ra = resample(a, 1, 2); 4 | fs = fs * 0.5; 5 | 6 | frame = ra(4001:4001+512); 7 | 8 | periodogram(frame, [], 'onesided', 2048, fs); 9 | hold on; 10 | pyulear(frame, 12, 2048, fs, 'onesided'); 11 | -------------------------------------------------------------------------------- /docs/src/index.rst: -------------------------------------------------------------------------------- 1 | ################### 2 | scikits.talkbox 3 | ################### 4 | 5 | 6 | .. htmlonly:: 7 | 8 | :Release: |version| 9 | :Date: |today| 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | intro 15 | spectral 16 | lpc 17 | 18 | .. Indices and tables 19 | .. ================== 20 | 21 | .. htmlonly:: 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/src/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | The following document describes how to use the talkbox scikits for signal 5 | processing. The document assumes basic knowledge of signal processing (Fourier 6 | Transform, Linear Time Invariant systems). 7 | 8 | Talkbox is set of python modules for speech/signal processing. The following features are planned before a 1.0 release: 9 | 10 | * Spectrum estimation related functions: both parametic (lpc, high 11 | resolution methods like music and co), and non-parametric (Welch, 12 | periodogram) 13 | * Fourier-like transforms (DCT, DST, MDCT, etc...) 14 | * Basic signal processing tasks such as resampling 15 | * Speech related functionalities: mfcc, mel spectrum, etc.. 16 | * More as it comes 17 | 18 | The goal of this toolbox is to be a sandbox for features which may end up in 19 | scipy at some point. I also want talkbox to be useful for both research and 20 | educational purpose. As such, a requirement is to have a pure python 21 | implementation for everything, with optional C/C++/Lisp for speed: reading 22 | signal processing in C is no fun, and neither is waiting for your mfcc 23 | computation one hour before ICASSP submission deadline :). 24 | 25 | Prerequisites 26 | ------------- 27 | 28 | Talkbox needs at least **Python 2.4** to run. It also needs Numpy_ and Scipy_, 29 | as well as setuptools. Any recent version of numpy (1.0 and higher), scipy (0.6 30 | and higher) should do. 31 | 32 | .. _Numpy: http://www.scipy.org 33 | .. _Scipy: http://www.scipy.org 34 | 35 | Other useful packages are matplotlib, and audiolab scikits. They are not 36 | mandatory, but will be assumed for the examples in this documentation. 37 | 38 | Installing talkbox 39 | ------------------ 40 | 41 | Unfortunately, at this point, your only option is to install talkbox from 42 | sources: 43 | 44 | .. code-block:: python 45 | :linenos: 46 | 47 | svn co http://svn.scipy.org/svn/scikits/trunk/talkbox 48 | 49 | You can install talkbox with the usual setup.py method in the talkbox source 50 | tree: 51 | 52 | .. code-block:: python 53 | :linenos: 54 | 55 | python setup.py install 56 | 57 | If you don't want to install it as a python egg (for stow, etc...), you can use: 58 | 59 | .. code-block:: python 60 | :linenos: 61 | 62 | python setup.py install --single-version-externally-managed --record=/dev/null 63 | 64 | Starting with talkbox 65 | --------------------- 66 | 67 | main namespace 68 | ^^^^^^^^^^^^^^ 69 | 70 | The main functions are available through the main scikits.talkbox namespace:: 71 | 72 | import scikits.talkbox as talk 73 | 74 | Getting help 75 | ^^^^^^^^^^^^ 76 | 77 | All high level functions are duely documented, and are available through the 78 | usual python facilities:: 79 | 80 | import scikits.talkbox as talk 81 | help(talk) 82 | 83 | Will give you online-help for the main talkbox functions. 84 | -------------------------------------------------------------------------------- /docs/src/lpc.rst: -------------------------------------------------------------------------------- 1 | Linear Prediction 2 | ================= 3 | 4 | The goal of linear-prediction is to model a signal as a linear combination of 5 | its past/future. 6 | 7 | Yule-Walker equations 8 | --------------------- 9 | 10 | For a discrete-time signal x, we want to approximate the sample x[n] as a 11 | linear combination xp[n] of the k preceding samples: 12 | 13 | xp[n] = -c[1] * x[n-2] - ... - c[k-1] * x[n-k-1] 14 | 15 | The best approximation in the mean-square sense is a tuple(c[1], ..., c[k]) 16 | such as the squared error: 17 | 18 | e = xp - x 19 | 20 | Is minimal. Noting p(x) = xp, and x^{-k} the signal x^{-k}[n] = x[n-k], since p 21 | is a linear combination of the (x^-1, ..., x^-k), we know that the error p(x) - 22 | x is minimal for p the orthogonal project of x on the vector space V spanned by 23 | the x^-1, ..., x^-k. In particular, the error e is then orthogonal to any 24 | vector in V: 25 | 26 | .. .. latex:: 27 | .. = 0 28 | .. = 0 29 | .. 30 | .. And: 31 | .. 32 | .. = > 33 | 34 | TODO: decent blob for above 35 | 36 | .. math:: 37 | 38 | \begin{pmatrix} 39 | -R[1] \\ 40 | -R[2] \\ 41 | \vdots \\ 42 | -R[p] 43 | \end{pmatrix} 44 | = 45 | \begin{pmatrix} 46 | R[0] & \overline{R}[1] & \dots & \overline{R}[p-1] \\ 47 | R[1] & R[0] & \ddots & \vdots \\ 48 | \vdots & \ddots & \ddots & \overline{R}[1]\\ 49 | R[p-1] & \dots & R[1] & R[0] 50 | \end{pmatrix} 51 | \begin{pmatrix} 52 | a[1] \\ 53 | \vdots \\ 54 | \vdots \\ 55 | a[p] 56 | \end{pmatrix} 57 | 58 | Levinson-Durbin recursion 59 | ------------------------- 60 | 61 | Levinson-Durbin recursion is a recursive algorithm to solve the Yule-Walker 62 | equations in O(p^2) instead of O(p^3) usually necessary to inverse a matrix. It 63 | uses the Hermitian-Toeplitz structure of the correlation matrix. 64 | 65 | .. autofunction:: scikits.talkbox.levinson 66 | 67 | Linear prediction coding 68 | ------------------------ 69 | 70 | Solve the Yule-Walker equation for a signal x, using the autocorelation method 71 | and Levinson-Durbin for the Yule-Walker inversion. 72 | 73 | .. autofunction:: scikits.talkbox.lpc 74 | -------------------------------------------------------------------------------- /docs/src/spectral.rst: -------------------------------------------------------------------------------- 1 | Spectral analysis 2 | ================= 3 | 4 | Spectral signal analysis is the field concerned with the estimation of the 5 | distribution of a signal (more exactly its power) in the frequency domain. 6 | 7 | The Power Spectrum Density (PSD) S_X of X_n is defined as the squared discrete 8 | time Fourier transform of X_n 9 | 10 | .. math:: \forall f \in \mathbb{R}, \qquad 11 | S_X = \left|{\sum_n{X_n e^{-2\pi j f n}}}\right|^2 12 | 13 | Under suitable technical conditions, this is equal to the discrete time Fourier 14 | transform of the autocorrelation function for stationary signals in the wide 15 | sense: 16 | 17 | .. math:: \forall f \in \mathbb{R}, \qquad S_X = \sum_n{\gamma(n) e^{-2\pi j f n}} 18 | 19 | This is called the power spectrum density because integrating it over the 20 | frequency domain gives you the average power of X and because it can be 21 | proved that S_X is always positive for any f. 22 | 23 | Spectral density estimation 24 | --------------------------- 25 | 26 | Since in practice, we only have finite signals, we need method to estimate the 27 | PSD. talkbox implements various methods for PSD estimation, which are 28 | classified in two broad classes: 29 | 30 | * non-parametric (general methods, estimate the PSD directly from the 31 | signal) 32 | * parametric (use an underlying signal model, for example AR; is less 33 | general, but generally more efficient in some sense if applicable). 34 | 35 | Non-parametric estimation 36 | ------------------------- 37 | 38 | Periodogram 39 | ^^^^^^^^^^^ 40 | 41 | The raw periodogram is a relatively straightforward estimator of the PSD. The 42 | raw periodogram I of a signal of length N is defined as: 43 | 44 | .. math:: I(f) \triangleq \frac{{|\sum_n{x[n] e^{-2\pi j k f/f_s}}|}^2}{fs N} 45 | 46 | where f_s is the sampling rate. In practice, the periodogram can only be 47 | computed on a frequency grid; the most commonly used grid is k/N (normalized 48 | frequencies) for k in [0, ..., N-1]. With this grid, the sum becomes a simple 49 | DFT which can be computed using the FFT. 50 | 51 | Examples 52 | """""""" 53 | 54 | As a first example, let's generate a simple sinusoid signal embedded into white 55 | noise:: 56 | 57 | import numpy as np 58 | import matplotlib.pyplot as plt 59 | from scikits.talkbox.spectral.basic import periodogram 60 | fs = 1000 61 | x = np.sin(2 * np.pi * 0.15 * fs * np.linspace(0., 0.3, 0.3 * fs)) 62 | x += 0.1 * np.random.randn(x.size) 63 | px, fx = periodogram(x, nfft=16384, fs=fs) 64 | plt.plot(fx, 10 * np.log10(px)) 65 | 66 | Plotting the log periodogram then gives: 67 | 68 | .. htmlonly:: 69 | .. image:: examples/periodogram_1.png 70 | 71 | The number of points used for the FFT has been set high to highlight the lobe, 72 | artefact of the rectangular window. 73 | 74 | .. autofunction:: scikits.talkbox.spectral.basic.periodogram 75 | 76 | Parametric estimation 77 | --------------------- 78 | 79 | ar method 80 | ^^^^^^^^^ 81 | 82 | TODO: To be implemented 83 | 84 | Useful complements 85 | ------------------ 86 | 87 | A random signal :math:`X` is said to be (strictly) stationary if its 88 | distribution does not depend on the time. For time-discrete signals :math:`X_n` 89 | of distribution :math:`F_{X_n}` , this is written: 90 | 91 | .. math:: \forall n, k, \qquad F_{X_n} = F_{X_{n+k}} 92 | 93 | Other stationary concepts can be defined, when only some moments of the 94 | signal do not depend on time. A widely used concept is weakly stationary 95 | signals. A signal is said to be weakly stationary (or stationary in the wide 96 | sense) if its means and covariance do not depend on time, and its covariance 97 | function :math:`\gamma(n, k)` depends only on the time difference n-k: 98 | 99 | .. math:: \forall n, k, \qquad \gamma(n, k) = 100 | \mathbb{E}[(X[n]-m)(X[k] -m)] = \gamma(n-k) 101 | 102 | -------------------------------------------------------------------------------- /pavement.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import shutil 5 | 6 | import sphinx 7 | 8 | import setuptools 9 | #import distutils 10 | import numpy.distutils 11 | 12 | try: 13 | from paver.tasks import VERSION as _PVER 14 | if not _PVER >= '1.0': 15 | raise RuntimeError("paver version >= 1.0 required (was %s)" % _PVER) 16 | except ImportError, e: 17 | raise RuntimeError("paver version >= 1.0 required") 18 | 19 | import paver 20 | import paver.doctools 21 | from paver.easy import Bunch, options, task, needs, dry, sh 22 | from paver.setuputils import setup 23 | 24 | import common 25 | 26 | setup( 27 | name=common.DISTNAME, 28 | namespace_packages=['scikits'], 29 | packages=setuptools.find_packages(), 30 | install_requires=common.INSTALL_REQUIRE, 31 | version=common.VERSION, 32 | include_package_data=True, 33 | ) 34 | 35 | options( 36 | sphinx=Bunch(builddir="build", sourcedir="src"), 37 | virtualenv=Bunch(script_name="install/bootstrap.py") 38 | ) 39 | 40 | def macosx_version(): 41 | st = subprocess.Popen(["sw_vers"], stdout=subprocess.PIPE) 42 | out = st.stdout.readlines() 43 | import re 44 | ver = re.compile("ProductVersion:\s+([0-9]+)\.([0-9]+)\.([0-9]+)") 45 | for i in out: 46 | m = ver.match(i) 47 | if m: 48 | return m.groups() 49 | 50 | def mpkg_name(): 51 | maj, min = macosx_version()[:2] 52 | pyver = ".".join([str(i) for i in sys.version_info[:2]]) 53 | return "scikits.talkbox-%s-py%s-macosx%s.%s.mpkg" % (common.build_fverstring(), 54 | pyver, maj, min) 55 | 56 | def tarball_name(): 57 | pyver = ".".join([str(i) for i in sys.version_info[:2]]) 58 | return "scikits.talkbox-%s.tar.gz" % (common.build_fverstring()) 59 | 60 | VPYEXEC = "install/bin/python" 61 | 62 | @task 63 | @needs('paver.virtual.bootstrap') 64 | def bootstrap(): 65 | """create virtualenv in ./install""" 66 | # XXX: fix the mkdir 67 | sh('mkdir -p install') 68 | sh('cd install; %s bootstrap.py' % sys.executable) 69 | 70 | @task 71 | @needs('bootstrap') 72 | def test_install(): 73 | """Install the package into the venv.""" 74 | sh('%s setup.py install' % VPYEXEC) 75 | 76 | @task 77 | def build_version_files(options): 78 | from common import write_version 79 | write_version(os.path.join("scikits", "talkbox", "version.py")) 80 | if os.path.exists(os.path.join("docs", "src")): 81 | write_version(os.path.join("docs", "src", "talkbox_version.py")) 82 | 83 | @task 84 | @needs('paver.sdist') 85 | def test_sdist(): 86 | """Test the tarball builds.""" 87 | sh('cd dist; tar -xzf %s' % tarball_name()) 88 | sh('cd dist/scikits.talkbox-%s; python setup.py build' % common.build_fverstring()) 89 | 90 | @task 91 | def clean(): 92 | for d in ['scikits.talkbox.egg-info', 'build', 'dist', 'install']: 93 | paver.path.path(d).rmtree() 94 | 95 | @task 96 | #@needs(['latex', 'html']) 97 | def dmg(): 98 | builddir = path("build") / "dmg" 99 | builddir.rmtree() 100 | builddir.mkdir() 101 | 102 | # Copy mpkg into image source 103 | mpkg_n = mpkg_name() 104 | mpkg = path("dist") / mpkg_n 105 | mpkg.copytree(builddir / mpkg_n) 106 | 107 | # Copy docs into image source 108 | doc_root = path(builddir) / "docs" 109 | html_docs = path("docs") / "html" 110 | pdf_docs = path("docs") / "pdf" / "talkbox.pdf" 111 | html_docs.copytree(doc_root / "html") 112 | pdf_docs.copy(doc_root / "talkbox.pdf") 113 | 114 | # Build the dmg 115 | image_name = "talkbox-%s.dmg" % common.build_fverstring() 116 | image = path(image_name) 117 | image.remove() 118 | cmd = ["hdiutil", "create", image_name, "-srcdir", str(builddir)] 119 | subprocess.Popen(cmd) 120 | #options.setup.package_data = 121 | # setuputils.find_package_data("scikits/talkbox", 122 | # package="scikits/talkbox", 123 | # only_in_packages=False) 124 | 125 | if paver.doctools.has_sphinx: 126 | def _latex_paths(): 127 | """look up the options that determine where all of the files are.""" 128 | opts = options 129 | docroot = paver.path.path(opts.get('docroot', 'docs')) 130 | if not docroot.exists(): 131 | raise BuildFailure("Sphinx documentation root (%s) does not exist." 132 | % docroot) 133 | builddir = docroot / opts.get("builddir", ".build") 134 | builddir.mkdir() 135 | srcdir = docroot / opts.get("sourcedir", "") 136 | if not srcdir.exists(): 137 | raise BuildFailure("Sphinx source file dir (%s) does not exist" 138 | % srcdir) 139 | latexdir = builddir / "latex" 140 | latexdir.mkdir() 141 | return Bunch(locals()) 142 | 143 | @task 144 | @needs('build_version_files') 145 | def latex(): 146 | """Build Audiolab's documentation and install it into 147 | scikits/talkbox/docs""" 148 | paths = _latex_paths() 149 | sphinxopts = ['', '-b', 'latex', paths.srcdir, paths.latexdir] 150 | dry("sphinx-build %s" % (" ".join(sphinxopts),), sphinx.main, sphinxopts) 151 | def build_latex(): 152 | subprocess.call(["make", "all-pdf"], cwd=paths.latexdir) 153 | dry("Build pdf doc", build_latex) 154 | destdir = paver.path.path("docs") / "pdf" 155 | destdir.rmtree() 156 | destdir.makedirs() 157 | pdf = paths.latexdir / "talkbox.pdf" 158 | pdf.move(destdir) 159 | 160 | @task 161 | @needs('build_version_files', 'paver.doctools.html') 162 | def html_build(): 163 | """Build Audiolab's html documentation.""" 164 | pass 165 | 166 | @task 167 | @needs(['html_build']) 168 | def html(): 169 | """Build Audiolab's documentation and install it into 170 | scikits/talkbox/docs""" 171 | builtdocs = paver.path.path("docs") / options.sphinx.builddir / "html" 172 | destdir = paver.path.path("docs") / "html" 173 | destdir.rmtree() 174 | builtdocs.move(destdir) 175 | 176 | @task 177 | @needs(['html', 'latex']) 178 | def doc(): 179 | pass 180 | 181 | @task 182 | @needs('setuptools.command.sdist') 183 | def sdist(options): 184 | """Build tarball.""" 185 | pass 186 | 187 | @task 188 | @needs(['doc', 'pavement.sdist']) 189 | def release_sdist(options): 190 | """Build doc + tarball.""" 191 | pass 192 | -------------------------------------------------------------------------------- /scikits/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /scikits/talkbox/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | 3 | from tools import * 4 | import tools 5 | __all__ += tools.__all__ 6 | 7 | import linpred 8 | from linpred import * 9 | __all__ += linpred.__all__ 10 | 11 | import version 12 | 13 | from numpy.testing import Tester 14 | 15 | test = Tester().test 16 | bench = Tester().bench 17 | -------------------------------------------------------------------------------- /scikits/talkbox/features/__init__.py: -------------------------------------------------------------------------------- 1 | from scikits.talkbox.features.mel import mel2hz, hz2mel 2 | __all__ = ['mel2hz', 'hz2mel'] 3 | 4 | from scikits.talkbox.features.mfcc import mfcc 5 | __all__ += ['mfcc'] 6 | -------------------------------------------------------------------------------- /scikits/talkbox/features/mel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def hz2mel(f): 4 | """Convert an array of frequency in Hz into mel.""" 5 | return 1127.01048 * np.log(f/700 +1) 6 | 7 | def mel2hz(m): 8 | """Convert an array of frequency in Hz into mel.""" 9 | return (np.exp(m / 1127.01048) - 1) * 700 10 | -------------------------------------------------------------------------------- /scikits/talkbox/features/mfcc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from scipy.io import loadmat 4 | from scipy.signal import lfilter, hamming 5 | from scipy.fftpack import fft 6 | from scipy.fftpack.realtransforms import dct 7 | 8 | from scikits.talkbox import segment_axis 9 | 10 | from mel import hz2mel 11 | 12 | def trfbank(fs, nfft, lowfreq, linsc, logsc, nlinfilt, nlogfilt): 13 | """Compute triangular filterbank for MFCC computation.""" 14 | # Total number of filters 15 | nfilt = nlinfilt + nlogfilt 16 | 17 | #------------------------ 18 | # Compute the filter bank 19 | #------------------------ 20 | # Compute start/middle/end points of the triangular filters in spectral 21 | # domain 22 | freqs = np.zeros(nfilt+2) 23 | freqs[:nlinfilt] = lowfreq + np.arange(nlinfilt) * linsc 24 | freqs[nlinfilt:] = freqs[nlinfilt-1] * logsc ** np.arange(1, nlogfilt + 3) 25 | heights = 2./(freqs[2:] - freqs[0:-2]) 26 | 27 | # Compute filterbank coeff (in fft domain, in bins) 28 | fbank = np.zeros((nfilt, nfft)) 29 | # FFT bins (in Hz) 30 | nfreqs = np.arange(nfft) / (1. * nfft) * fs 31 | for i in range(nfilt): 32 | low = freqs[i] 33 | cen = freqs[i+1] 34 | hi = freqs[i+2] 35 | 36 | lid = np.arange(np.floor(low * nfft / fs) + 1, 37 | np.floor(cen * nfft / fs) + 1, dtype=np.int) 38 | lslope = heights[i] / (cen - low) 39 | rid = np.arange(np.floor(cen * nfft / fs) + 1, 40 | np.floor(hi * nfft / fs) + 1, dtype=np.int) 41 | rslope = heights[i] / (hi - cen) 42 | fbank[i][lid] = lslope * (nfreqs[lid] - low) 43 | fbank[i][rid] = rslope * (hi - nfreqs[rid]) 44 | 45 | return fbank, freqs 46 | 47 | def mfcc(input, nwin=256, nfft=512, fs=16000, nceps=13): 48 | """Compute Mel Frequency Cepstral Coefficients. 49 | 50 | Parameters 51 | ---------- 52 | input: ndarray 53 | input from which the coefficients are computed 54 | 55 | Returns 56 | ------- 57 | ceps: ndarray 58 | Mel-cepstrum coefficients 59 | mspec: ndarray 60 | Log-spectrum in the mel-domain. 61 | 62 | Notes 63 | ----- 64 | MFCC are computed as follows: 65 | * Pre-processing in time-domain (pre-emphasizing) 66 | * Compute the spectrum amplitude by windowing with a Hamming window 67 | * Filter the signal in the spectral domain with a triangular 68 | filter-bank, whose filters are approximatively linearly spaced on the 69 | mel scale, and have equal bandwith in the mel scale 70 | * Compute the DCT of the log-spectrum 71 | 72 | References 73 | ---------- 74 | .. [1] S.B. Davis and P. Mermelstein, "Comparison of parametric 75 | representations for monosyllabic word recognition in continuously 76 | spoken sentences", IEEE Trans. Acoustics. Speech, Signal Proc. 77 | ASSP-28 (4): 357-366, August 1980.""" 78 | 79 | # MFCC parameters: taken from auditory toolbox 80 | over = nwin - 160 81 | # Pre-emphasis factor (to take into account the -6dB/octave rolloff of the 82 | # radiation at the lips level) 83 | prefac = 0.97 84 | 85 | #lowfreq = 400 / 3. 86 | lowfreq = 133.33 87 | #highfreq = 6855.4976 88 | linsc = 200/3. 89 | logsc = 1.0711703 90 | 91 | nlinfil = 13 92 | nlogfil = 27 93 | nfil = nlinfil + nlogfil 94 | 95 | w = hamming(nwin, sym=0) 96 | 97 | fbank = trfbank(fs, nfft, lowfreq, linsc, logsc, nlinfil, nlogfil)[0] 98 | 99 | #------------------ 100 | # Compute the MFCC 101 | #------------------ 102 | extract = preemp(input, prefac) 103 | framed = segment_axis(extract, nwin, over) * w 104 | 105 | # Compute the spectrum magnitude 106 | spec = np.abs(fft(framed, nfft, axis=-1)) 107 | # Filter the spectrum through the triangle filterbank 108 | mspec = np.log10(np.dot(spec, fbank.T)) 109 | # Use the DCT to 'compress' the coefficients (spectrum -> cepstrum domain) 110 | ceps = dct(mspec, type=2, norm='ortho', axis=-1)[:, :nceps] 111 | 112 | return ceps, mspec, spec 113 | 114 | def preemp(input, p): 115 | """Pre-emphasis filter.""" 116 | return lfilter([1., -p], 1, input) 117 | 118 | if __name__ == '__main__': 119 | extract = loadmat('extract.mat')['extract'] 120 | ceps = mfcc(extract) 121 | -------------------------------------------------------------------------------- /scikits/talkbox/features/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import numpy as np 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration 6 | config = Configuration('features', parent_package, top_path) 7 | 8 | return config 9 | 10 | if __name__ == '__main__': 11 | from numpy.distutils.core import setup 12 | setup(**configuration(top_path='').todict()) 13 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | 3 | from common import * 4 | import common 5 | __all__ += common.__all__ 6 | 7 | from levinson_lpc import * 8 | import levinson_lpc 9 | __all__ += levinson_lpc.__all__ 10 | 11 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/common.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from scipy.signal import lfilter 4 | 5 | #from scikits.talkbox.linpred import lpc 6 | from scikits.talkbox.linpred.levinson_lpc import levinson, lpc 7 | from scikits.talkbox.tools import slfilter 8 | from scikits.talkbox.tools.cacorr import acorr 9 | 10 | __all__ = ["lpcres"] 11 | 12 | def lpcres(signal, order, usefft=True): 13 | """Compute the LPC residual of a signal. 14 | 15 | The LPC residual is the 'error' signal from LPC analysis, and is defined 16 | as: 17 | 18 | res[n] = x[n] - xe[n] = 1 + a[1] x[n-1] + ... + a[p] x[n-p] 19 | 20 | Where x is the input signal and xe the linear prediction of x. 21 | 22 | Parameters 23 | ---------- 24 | signal : array-like 25 | input signal. If rank of signal is 2, each row is assumed to be an 26 | independant signal on which the LPC is computed. Rank > 2 is not 27 | supported. 28 | order : int 29 | LPC order 30 | 31 | Returns 32 | ------- 33 | res : array-like 34 | LPC residual 35 | 36 | Note 37 | ---- 38 | The LPC residual can also be seen as the input of the LPC analysis filter. 39 | As the LPC filter is a whitening filter, it is a whitened version of the 40 | signal. 41 | 42 | In AR modelling, the residual is simply the estimated excitation of the AR 43 | filter. 44 | """ 45 | if signal.ndim == 1: 46 | return lfilter(lpc(signal, order)[0], 1., signal) 47 | elif signal.ndim == 2: 48 | if usefft: 49 | cf = lpc(signal, order, axis=-1)[0] 50 | else: 51 | c = acorr(signal, maxlag=order, onesided=True)/signal.shape[-1] 52 | cf = levinson(c, order, axis=-1)[0] 53 | return slfilter(cf, np.ones((cf.shape[0], 1)), signal) 54 | else: 55 | raise ValueError("Input of rank > 2 not supported yet") 56 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/levinson_lpc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Last Change: Wed Sep 24 06:00 PM 2008 J 4 | 5 | import numpy as np 6 | from scipy.fftpack import fft, ifft 7 | 8 | from scikits.talkbox.tools import nextpow2 9 | 10 | from scikits.talkbox.linpred._lpc import levinson as c_levinson 11 | 12 | __all__ = ['levinson', 'lpc'] 13 | 14 | def lpc(signal, order, axis=-1): 15 | """Compute the Linear Prediction Coefficients. 16 | 17 | Return the order + 1 LPC coefficients for the signal. c = lpc(x, k) will 18 | find the k+1 coefficients of a k order linear filter: 19 | 20 | xp[n] = -c[1] * x[n-2] - ... - c[k-1] * x[n-k-1] 21 | 22 | Such as the sum of the squared-error e[i] = xp[i] - x[i] is minimized. 23 | 24 | Parameters 25 | ---------- 26 | signal: array_like 27 | input signal 28 | order : int 29 | LPC order (the output will have order + 1 items) 30 | 31 | Returns 32 | ------- 33 | a : array-like 34 | the solution of the inversion. 35 | e : array-like 36 | the prediction error. 37 | k : array-like 38 | reflection coefficients. 39 | 40 | Notes 41 | ----- 42 | This uses Levinson-Durbin recursion for the autocorrelation matrix 43 | inversion, and fft for the autocorrelation computation. 44 | 45 | For small order, particularly if order << signal size, direct computation 46 | of the autocorrelation is faster: use levinson and correlate in this case.""" 47 | n = signal.shape[axis] 48 | if order > n: 49 | raise ValueError("Input signal must have length >= order") 50 | 51 | r = acorr_lpc(signal, axis) 52 | return levinson(r, order, axis) 53 | 54 | def _acorr_last_axis(x, nfft, maxlag): 55 | a = np.real(ifft(np.abs(fft(x, n=nfft) ** 2))) 56 | return a[..., :maxlag+1] / x.shape[-1] 57 | 58 | def acorr_lpc(x, axis=-1): 59 | """Compute autocorrelation of x along the given axis. 60 | 61 | This compute the biased autocorrelation estimator (divided by the size of 62 | input signal) 63 | 64 | Notes 65 | ----- 66 | The reason why we do not use acorr directly is for speed issue.""" 67 | if not np.isrealobj(x): 68 | raise ValueError("Complex input not supported yet") 69 | 70 | maxlag = x.shape[axis] 71 | nfft = 2 ** nextpow2(2 * maxlag - 1) 72 | 73 | if axis != -1: 74 | x = np.swapaxes(x, -1, axis) 75 | a = _acorr_last_axis(x, nfft, maxlag) 76 | if axis != -1: 77 | a = np.swapaxes(a, -1, axis) 78 | return a 79 | 80 | def levinson(r, order, axis = -1): 81 | """Levinson-Durbin recursion, to efficiently solve symmetric linear systems 82 | with toeplitz structure. 83 | 84 | Parameters 85 | ---------- 86 | r : array-like 87 | input array to invert (since the matrix is symmetric Toeplitz, the 88 | corresponding pxp matrix is defined by p items only). Generally the 89 | autocorrelation of the signal for linear prediction coefficients 90 | estimation. The first item must be a non zero real, and corresponds 91 | to the autocorelation at lag 0 for linear prediction. 92 | order : int 93 | order of the recursion. For order p, you will get p+1 coefficients. 94 | axis : int, optional 95 | axis over which the algorithm is applied. -1 by default. 96 | 97 | Returns 98 | ------- 99 | a : array-like 100 | the solution of the inversion (see notes). 101 | e : array-like 102 | the prediction error. 103 | k : array-like 104 | reflection coefficients. 105 | 106 | Notes 107 | ----- 108 | Levinson is a well-known algorithm to solve the Hermitian toeplitz 109 | equation: :: 110 | 111 | _ _ 112 | -R[1] = R[0] R[1] ... R[p-1] a[1] 113 | : : : : : 114 | : : : _ * : 115 | -R[p] = R[p-1] R[p-2] ... R[0] a[p] 116 | 117 | with respect to a. Using the special symmetry in the matrix, the inversion 118 | can be done in O(p^2) instead of O(p^3). 119 | 120 | Only double argument are supported: float and long double are internally 121 | converted to double, and complex input are not supported at all. 122 | """ 123 | if axis != -1: 124 | r = np.swapaxes(r, axis, -1) 125 | a, e, k = c_levinson(r, order) 126 | if axis != -1: 127 | a = np.swapaxes(a, axis, -1) 128 | e = np.swapaxes(e, axis, -1) 129 | k = np.swapaxes(k, axis, -1) 130 | return a, e, k 131 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/py_lpc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Last Change: Wed Sep 24 05:00 PM 2008 J 4 | 5 | import numpy as np 6 | import scipy as sp 7 | import scipy.signal as sig 8 | 9 | def lpc_ref(signal, order): 10 | """Compute the Linear Prediction Coefficients. 11 | 12 | Return the order + 1 LPC coefficients for the signal. c = lpc(x, k) will 13 | find the k+1 coefficients of a k order linear filter: 14 | 15 | xp[n] = -c[1] * x[n-2] - ... - c[k-1] * x[n-k-1] 16 | 17 | Such as the sum of the squared-error e[i] = xp[i] - x[i] is minimized. 18 | 19 | Parameters 20 | ---------- 21 | signal: array_like 22 | input signal 23 | order : int 24 | LPC order (the output will have order + 1 items) 25 | 26 | Notes 27 | ---- 28 | This is just for reference, as it is using the direct inversion of the 29 | toeplitz matrix, which is really slow""" 30 | if signal.ndim > 1: 31 | raise ValueError("Array of rank > 1 not supported yet") 32 | if order > signal.size: 33 | raise ValueError("Input signal must have a lenght >= lpc order") 34 | 35 | if order > 0: 36 | p = order + 1 37 | r = np.zeros(p, signal.dtype) 38 | # Number of non zero values in autocorrelation one needs for p LPC 39 | # coefficients 40 | nx = np.min([p, signal.size]) 41 | x = np.correlate(signal, signal, 'full') 42 | r[:nx] = x[signal.size-1:signal.size+order] 43 | phi = np.dot(sp.linalg.inv(sp.linalg.toeplitz(r[:-1])), -r[1:]) 44 | return np.concatenate(([1.], phi)) 45 | else: 46 | return np.ones(1, dtype = signal.dtype) 47 | 48 | def levinson_1d(r, order): 49 | """Levinson-Durbin recursion, to efficiently solve symmetric linear systems 50 | with toeplitz structure. 51 | 52 | Parameters 53 | --------- 54 | r : array-like 55 | input array to invert (since the matrix is symmetric Toeplitz, the 56 | corresponding pxp matrix is defined by p items only). Generally the 57 | autocorrelation of the signal for linear prediction coefficients 58 | estimation. The first item must be a non zero real. 59 | 60 | Notes 61 | ---- 62 | This implementation is in python, hence unsuitable for any serious 63 | computation. Use it as educational and reference purpose only. 64 | 65 | Levinson is a well-known algorithm to solve the Hermitian toeplitz 66 | equation: 67 | 68 | _ _ 69 | -R[1] = R[0] R[1] ... R[p-1] a[1] 70 | : : : : * : 71 | : : : _ * : 72 | -R[p] = R[p-1] R[p-2] ... R[0] a[p] 73 | _ 74 | with respect to a ( is the complex conjugate). Using the special symmetry 75 | in the matrix, the inversion can be done in O(p^2) instead of O(p^3). 76 | """ 77 | r = np.atleast_1d(r) 78 | if r.ndim > 1: 79 | raise ValueError("Only rank 1 are supported for now.") 80 | 81 | n = r.size 82 | if n < 1: 83 | raise ValueError("Cannot operate on empty array !") 84 | elif order > n - 1: 85 | raise ValueError("Order should be <= size-1") 86 | 87 | if not np.isreal(r[0]): 88 | raise ValueError("First item of input must be real.") 89 | elif not np.isfinite(1/r[0]): 90 | raise ValueError("First item should be != 0") 91 | 92 | # Estimated coefficients 93 | a = np.empty(order+1, r.dtype) 94 | # temporary array 95 | t = np.empty(order+1, r.dtype) 96 | # Reflection coefficients 97 | k = np.empty(order, r.dtype) 98 | 99 | a[0] = 1. 100 | e = r[0] 101 | 102 | for i in xrange(1, order+1): 103 | acc = r[i] 104 | for j in range(1, i): 105 | acc += a[j] * r[i-j] 106 | k[i-1] = -acc / e 107 | a[i] = k[i-1] 108 | 109 | for j in range(order): 110 | t[j] = a[j] 111 | 112 | for j in range(1, i): 113 | a[j] += k[i-1] * np.conj(t[i-j]) 114 | 115 | e *= 1 - k[i-1] * np.conj(k[i-1]) 116 | 117 | return a, e, k 118 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import numpy as np 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration 6 | config = Configuration('linpred', parent_package, top_path) 7 | 8 | config.add_library('clpc', sources=['src/levinson.c']) 9 | 10 | config.add_extension('_lpc', sources=["src/_lpc.c"], 11 | include_dirs=[np.get_include()], 12 | libraries=["clpc"]) 13 | config.add_data_dir('tests') 14 | 15 | return config 16 | 17 | if __name__ == '__main__': 18 | from numpy.distutils.core import setup 19 | setup(**configuration(top_path='').todict()) 20 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/src/_lpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A set of python wrappers around C levinson, etc... 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "levinson.h" 9 | 10 | /* 11 | * Levinson-Durbin recursion on one array. Output arrays are put into 12 | * alpccoeff, klpccoeff and elpc. 13 | */ 14 | int array_levinson_1d(PyArrayObject *arr, long order, PyArrayObject** alpccoeff, 15 | PyArrayObject **klpccoeff, PyArrayObject **elpc) 16 | { 17 | double *tmp; 18 | npy_intp alpc_size = (order + 1); 19 | npy_intp klpc_size = order; 20 | npy_intp elpc_size = 1; 21 | 22 | *alpccoeff = (PyArrayObject*)PyArray_SimpleNew(1, &alpc_size, 23 | PyArray_DOUBLE); 24 | if(*alpccoeff == NULL) { 25 | return -1; 26 | } 27 | 28 | *klpccoeff = (PyArrayObject*)PyArray_SimpleNew(1, &klpc_size, 29 | NPY_DOUBLE); 30 | if(*klpccoeff == NULL) { 31 | goto clean_alpccoeff; 32 | } 33 | 34 | *elpc = (PyArrayObject*)PyArray_SimpleNew(1, &elpc_size, NPY_DOUBLE); 35 | if(*elpc == NULL) { 36 | goto clean_klpccoeff; 37 | } 38 | 39 | tmp = malloc(sizeof(*tmp) * order); 40 | if (tmp == NULL) { 41 | goto clean_elpc; 42 | } 43 | 44 | levinson((double*)(arr->data), order, 45 | (double*)((*alpccoeff)->data), (double*)((*elpc)->data), 46 | (double*)((*klpccoeff)->data), tmp); 47 | 48 | free(tmp); 49 | 50 | return 0; 51 | 52 | clean_elpc: 53 | Py_DECREF(*elpc); 54 | clean_klpccoeff: 55 | Py_DECREF(*klpccoeff); 56 | clean_alpccoeff: 57 | Py_DECREF(*alpccoeff); 58 | return -1; 59 | } 60 | 61 | int array_levinson_nd(PyArrayObject *arr, long order, 62 | PyArrayObject** alpccoeff, 63 | PyArrayObject **klpccoeff, PyArrayObject **elpc) 64 | { 65 | double *acoeff, *kcoeff, *tmp; 66 | double *err; 67 | double *data; 68 | npy_int rank; 69 | npy_intp alpc_size[NPY_MAXDIMS]; 70 | npy_intp klpc_size[NPY_MAXDIMS]; 71 | npy_intp elpc_size[NPY_MAXDIMS]; 72 | npy_int n, nrepeat; 73 | int i; 74 | 75 | rank = PyArray_NDIM(arr); 76 | if (rank < 2) { 77 | return -1; 78 | } 79 | 80 | nrepeat = 1; 81 | for (i = 0; i < rank - 1; ++i) { 82 | nrepeat *= PyArray_DIM(arr, i); 83 | alpc_size[i] = PyArray_DIM(arr, i); 84 | klpc_size[i] = PyArray_DIM(arr, i); 85 | elpc_size[i] = PyArray_DIM(arr, i); 86 | } 87 | alpc_size[rank-1] = order + 1; 88 | klpc_size[rank-1] = order; 89 | 90 | *alpccoeff = (PyArrayObject*)PyArray_SimpleNew(rank, alpc_size, 91 | PyArray_DOUBLE); 92 | if(*alpccoeff == NULL) { 93 | return -1; 94 | } 95 | 96 | *klpccoeff = (PyArrayObject*)PyArray_SimpleNew(rank, klpc_size, 97 | NPY_DOUBLE); 98 | if(*klpccoeff == NULL) { 99 | goto clean_alpccoeff; 100 | } 101 | 102 | *elpc = (PyArrayObject*)PyArray_SimpleNew(rank-1, elpc_size, NPY_DOUBLE); 103 | if(*elpc == NULL) { 104 | goto clean_klpccoeff; 105 | } 106 | 107 | tmp = malloc(sizeof(*tmp) * order); 108 | if (tmp == NULL) { 109 | goto clean_elpc; 110 | } 111 | 112 | data = (double*)arr->data; 113 | acoeff = (double*)((*alpccoeff)->data); 114 | kcoeff = (double*)((*klpccoeff)->data); 115 | err = (double*)((*elpc)->data); 116 | n = PyArray_DIM(arr, rank-1); 117 | for(i = 0; i < nrepeat; ++i) { 118 | levinson(data, order, acoeff, err, kcoeff, tmp); 119 | data += n; 120 | acoeff += order + 1; 121 | kcoeff += order; 122 | err += 1; 123 | } 124 | 125 | free(tmp); 126 | return 0; 127 | 128 | clean_elpc: 129 | Py_DECREF(*elpc); 130 | clean_klpccoeff: 131 | Py_DECREF(*klpccoeff); 132 | clean_alpccoeff: 133 | Py_DECREF(*alpccoeff); 134 | return -1; 135 | } 136 | 137 | PyObject* array_levinson(PyObject* in, long order) 138 | { 139 | npy_intp rank, size, n; 140 | PyArrayObject *arr; 141 | PyObject *out = NULL; 142 | PyArrayObject *alpc, *klpc, *err; 143 | 144 | arr = (PyArrayObject*)PyArray_ContiguousFromObject(in, PyArray_DOUBLE, 145 | 1, 0); 146 | if (arr == NULL) { 147 | return NULL; 148 | } 149 | 150 | size = PyArray_SIZE(arr); 151 | if (size < 1) { 152 | PyErr_SetString(PyExc_ValueError, "Cannot operate on empty array !"); 153 | goto fail; 154 | } 155 | 156 | rank = PyArray_NDIM(arr); 157 | n = PyArray_DIM(arr, rank-1); 158 | if (n <= order) { 159 | PyErr_SetString(PyExc_ValueError, "Order should be <= size-1"); 160 | goto fail; 161 | } 162 | 163 | switch(rank) { 164 | case 1: 165 | array_levinson_1d(arr, order, &alpc, &klpc, &err); 166 | break; 167 | default: 168 | /* Iteratively run levinson recursion on last axis */ 169 | array_levinson_nd(arr, order, &alpc, &klpc, &err); 170 | break; 171 | } 172 | Py_DECREF(arr); 173 | 174 | out = PyTuple_New(3); 175 | 176 | PyTuple_SET_ITEM(out, 0, (PyObject*)alpc); 177 | PyTuple_SET_ITEM(out, 1, (PyObject*)err); 178 | PyTuple_SET_ITEM(out, 2, (PyObject*)klpc); 179 | 180 | return out; 181 | 182 | fail: 183 | Py_DECREF(arr); 184 | return NULL; 185 | } 186 | 187 | PyObject* PyArray_Levinson(PyObject* self, PyObject* args) 188 | { 189 | long order; 190 | PyObject *in = NULL; 191 | PyObject *out = NULL; 192 | 193 | if (!PyArg_ParseTuple(args, "Ol", &in, &order)) { 194 | return NULL; 195 | } 196 | 197 | out = array_levinson(in, order); 198 | if (out == NULL) { 199 | if (!PyErr_ExceptionMatches(PyExc_ValueError)) { 200 | return NULL; 201 | } 202 | } 203 | 204 | return out; 205 | } 206 | 207 | static PyMethodDef lpcmethods[] = { 208 | {"levinson", PyArray_Levinson, METH_VARARGS, NULL} 209 | }; 210 | 211 | PyMODINIT_FUNC init_lpc(void) 212 | { 213 | Py_InitModule("_lpc", lpcmethods); 214 | import_array(); 215 | } 216 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/src/levinson.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Last Change: Mon Sep 08 11:00 PM 2008 J 3 | */ 4 | #include 5 | #include /* for isfinite */ 6 | #include 7 | 8 | #include "levinson.h" 9 | 10 | /* 11 | * The actual computation : 12 | * - in : the input vector which defines the toeplitz matrix 13 | * - size : size of in (ie number of elements) 14 | * - order : size of the system to solve. order must be < size -1 15 | * - acoeff: solution (ie ar coefficients). Size must be at last order+1 16 | * - err : *prediction* error (scalar) 17 | * - kcoeff: reflexion coefficients. Size must be at last equal to equal to order. 18 | * - tmp : cache, mnust have at least order elements 19 | * 20 | * this function assume all arrays are allocated with the right size, and that 21 | * the parameters make sense. No checking is done, must be done before calling 22 | * this function: in particular, in[0] must be non zero. 23 | * 24 | * Returns 0 on success, -1 if a compuation error happened (overflow, underflow 25 | * for error calculation) 26 | */ 27 | 28 | int levinson(const double* in, int order, double* acoeff, double* err, 29 | double* kcoeff, double* tmp) 30 | { 31 | int i, j; 32 | double acc; 33 | int ret = 0; 34 | 35 | /* order 0 */ 36 | acoeff[0] = (double)1.0; 37 | *err = in[0]; 38 | 39 | /* order >= 1 */ 40 | for (i = 1; i <= order; ++i) { 41 | acc = in[i]; 42 | for ( j = 1; j <= i-1; ++j) { 43 | acc += acoeff[j]*in[i-j]; 44 | } 45 | kcoeff[i-1] = -acc/(*err); 46 | acoeff[i] = kcoeff[i-1]; 47 | 48 | for (j = 0; j < order; ++j) { 49 | tmp[j] = acoeff[j]; 50 | } 51 | 52 | for (j = 1; j < i; ++j) { 53 | acoeff[j] += kcoeff[i-1]*tmp[i-j]; 54 | } 55 | *err *= (1-kcoeff[i-1]*kcoeff[i-1]); 56 | } 57 | 58 | return ret; 59 | } 60 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/src/levinson.h: -------------------------------------------------------------------------------- 1 | #ifndef _TALKBOX_LEVINSON_C_ 2 | #define _TALKBOX_LEVINSON_C_ 3 | 4 | int levinson(const double* in, int order, double* acoeff, double* err, 5 | double* kcoeff, double* tmp); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/tests/gentest.m: -------------------------------------------------------------------------------- 1 | % Matlab script to generate test data 2 | 3 | x = reshape(linspace(1, 88, 88), 11, 8).'; 4 | 5 | x1_0 = levinson(x.', 0); 6 | x1_1 = levinson(x.', 1); 7 | x1_5 = levinson(x.', 5); 8 | x1_10 = levinson(x.', 10); 9 | 10 | x0_0 = levinson(x, 0).'; 11 | x0_1 = levinson(x, 1).'; 12 | x0_5 = levinson(x, 5).'; 13 | x0_7 = levinson(x, 7).'; 14 | 15 | save('lpc5.mat', 'x0_0', 'x0_1', 'x0_5', 'x0_7'); 16 | save('lpc5.mat', 'x1_0', 'x1_1', 'x1_5', 'x1_10', '-append'); 17 | save('lpc5.mat', 'x', '-append'); 18 | -------------------------------------------------------------------------------- /scikits/talkbox/linpred/tests/test_lpc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import * 3 | from unittest import TestCase 4 | 5 | from scipy.signal import lfilter 6 | 7 | from scikits.talkbox.tools import acorr 8 | 9 | from scikits.talkbox.linpred.py_lpc import lpc_ref, levinson_1d as py_levinson 10 | from scikits.talkbox.linpred._lpc import levinson as c_levinson 11 | from scikits.talkbox.linpred.levinson_lpc import levinson, acorr_lpc, lpc 12 | from scikits.talkbox.linpred.common import lpcres 13 | 14 | def test_acorr_lpc(): 15 | x = np.random.randn(12, 5) 16 | y = acorr_lpc(x) 17 | assert_array_equal(y[:, :5], acorr(x, onesided=True)/5) 18 | assert_array_almost_equal(y[:, 5], np.zeros(y.shape[0], y.dtype)) 19 | 20 | y = acorr_lpc(x, axis=0) 21 | assert_array_equal(y[:12,:], acorr(x, axis=0, onesided=True)/12) 22 | assert_array_almost_equal(y[12, 0], np.zeros((1, y.shape[1]), y.dtype)) 23 | 24 | class _TestLPCCommon(TestCase): 25 | # Values taken from matlab LPC 26 | X = np.linspace(0, 10, 11) 27 | X0 = np.array([1.]) 28 | X1 = np.array([1., -0.85714285714286]) 29 | X2 = np.array([1., -0.91468531468532, 0.06713286713287]) 30 | X5 = np.array([1., -0.90444817959784, 0.01158960447639, 0.01191036566532, 31 | 0.01244767895726, 0.04749964947373]) 32 | X10 = np.array([1., -0.90129704880529, 0.01182431679473, 0.01215800819372, 33 | 0.01271275428716, 0.01349864136763, 0.01452995829115, 34 | 0.01582545627451, 0.01740868982649, 0.01930844501169, 35 | -0.03162073915296]) 36 | X11 = np.array([1., -0.89945522397677, 0.01069965069345, 0.01114399783175, 37 | 0.01179096311245, 0.01265230954064, 0.01374369796049, 38 | 0.01508497179779, 0.01670051784961, 0.01861970968050, 39 | 0.02087744168741, -0.05824736795705]) 40 | 41 | def setUp(self): 42 | self.lpc_func = lpc_ref 43 | 44 | class TestLPCPyBackend(_TestLPCCommon): 45 | def setUp(self): 46 | self.lpc_func = lpc_ref 47 | 48 | def test_order0(self): 49 | """Testing lpc ref order 0.""" 50 | assert_array_almost_equal(self.X0, self.lpc_func(self.X, 0)) 51 | 52 | def test_order1(self): 53 | """Testing lpc ref order 1.""" 54 | assert_array_almost_equal(self.X1, self.lpc_func(self.X, 1)) 55 | 56 | def test_order2(self): 57 | """Testing lpc ref order 2.""" 58 | assert_array_almost_equal(self.X2, self.lpc_func(self.X, 2)) 59 | 60 | def test_order5(self): 61 | """Testing lpc ref order 5.""" 62 | assert_array_almost_equal(self.X5, self.lpc_func(self.X, 5)) 63 | 64 | def test_order10(self): 65 | """Testing lpc ref order 10.""" 66 | assert_array_almost_equal(self.X10, self.lpc_func(self.X, 10)) 67 | 68 | def test_order11(self): 69 | """Testing lpc ref order 11.""" 70 | assert_array_almost_equal(self.X11, self.lpc_func(self.X, 11)) 71 | 72 | class TestLPCHighBackend(_TestLPCCommon): 73 | E0 = np.array([35.]) 74 | E1 = np.array([9.28571428571428]) 75 | E5 = np.array([9.15927742966687]) 76 | E10 = np.array([9.13002661779096]) 77 | 78 | def setUp(self): 79 | from scikits.talkbox.linpred.levinson_lpc import lpc 80 | self.lpc = lpc 81 | 82 | def test_pred_err(self): 83 | err = self.lpc(self.X, 0)[1] 84 | assert_array_almost_equal(self.E0, err) 85 | 86 | err = self.lpc(self.X, 1)[1] 87 | assert_array_almost_equal(self.E1, err) 88 | 89 | err = self.lpc(self.X, 5)[1] 90 | assert_array_almost_equal(self.E5, err) 91 | 92 | err = self.lpc(self.X, 10)[1] 93 | assert_array_almost_equal(self.E10, err) 94 | 95 | def test_order0(self): 96 | """Testing actual lpc order 0.""" 97 | assert_array_almost_equal(self.X0, self.lpc(self.X, 0)[0]) 98 | 99 | def test_order1(self): 100 | """Testing actual lpc order 1.""" 101 | assert_array_almost_equal(self.X1, self.lpc(self.X, 1)[0]) 102 | 103 | def test_order2(self): 104 | """Testing actual lpc order 2.""" 105 | assert_array_almost_equal(self.X2, self.lpc(self.X, 2)[0]) 106 | 107 | def test_order5(self): 108 | """Testing actual lpc order 5.""" 109 | assert_array_almost_equal(self.X5, self.lpc(self.X, 5)[0]) 110 | 111 | def test_order10(self): 112 | """Testing actual lpc order 10.""" 113 | assert_array_almost_equal(self.X10, self.lpc(self.X, 10)[0]) 114 | 115 | def test_order11(self): 116 | """Testing actual lpc order 11.""" 117 | assert_array_almost_equal(self.X11, self.lpc(self.X, 11)[0]) 118 | 119 | def test_axisdef(self): 120 | """Testing lpc of matrix, along default axis.""" 121 | xm = np.vstack((self.X, self.X)) 122 | re = {0: self.X0, 1: self.X1, 2: self.X1, 5: self.X5, 10: self.X10, 123 | 11: self.X11} 124 | for order in [0, 1, 5, 10, 11]: 125 | am = self.lpc(xm, order=order)[0] 126 | for i in range(2): 127 | assert_array_almost_equal(am[i], re[order]) 128 | 129 | def test_axis0(self): 130 | """Testing lpc of matrix, along axis 0.""" 131 | xm = np.vstack((self.X, self.X)).T 132 | re = {0: self.X0.T, 1: self.X1.T, 2: self.X1.T, 5: self.X5.T, 133 | 10: self.X10.T, 11: self.X11.T} 134 | 135 | for order in [0, 1, 5, 10, 11]: 136 | am = self.lpc(xm, order=order, axis=0)[0] 137 | for i in range(2): 138 | assert_array_almost_equal(am[:, i], re[order]) 139 | 140 | class _LevinsonCommon(TestCase): 141 | X = np.linspace(1, 11, 11) 142 | X0 = np.array([1.]) 143 | E0 = np.array([1.]) 144 | X1 = np.array([1, -2.]) 145 | E1 = np.array([-3.]) 146 | X5 = np.array([1, -1.166666666667, 0., 0., -0., -0.166666666667]) 147 | E5 = np.array([-7/3.]) 148 | X10 = np.array([1., -1.0909090909, 0, 0, 0, 0, 0, 0, 0, 0, -0.09090909]) 149 | E10 = np.array([-2.181818181818181818]) 150 | 151 | Xc = np.linspace(1, 11, 11) + 1.j * np.linspace(0, 10, 11) 152 | Xc1 = np.array([1, -2-1j]) 153 | Xc5 = np.array([1., -1.2, 0, 0, 0, -0.2000j]) 154 | Xc10 = np.array([1., -1.1, 0, 0, 0, 0, 0, 0, 0, 0, -0.1j]) 155 | 156 | Xm = np.linspace(1, 11 * 8, 11 * 8).reshape(8, 11) 157 | Xm0_a0 = np.ones(11, dtype = Xm.dtype)[None, :] 158 | Xm1_a0 = np.array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], 159 | [-12., -6.5, -4.66666667,-3.75, -3.2, -2.83333333, 160 | -2.57142857, -2.375, -2.22222222, -2.1, -2.]]) 161 | 162 | Xm7_a0 = np.array([[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 163 | 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 164 | 1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 165 | 1.00000000e+00, 1.00000000e+00], 166 | [-1.16176471e+00, -1.15714286e+00, -1.15277778e+00, 167 | -1.14864865e+00, -1.14473684e+00, -1.14102564e+00, 168 | -1.13750000e+00, -1.13414634e+00, -1.13095238e+00, 169 | -1.12790698e+00, -1.12500000e+00], 170 | [ 3.77160714e-16, 2.69210799e-16, -3.13275572e-16, 171 | -8.29044746e-17, 2.95247607e-16, -4.08063714e-16, 172 | -4.68392740e-16, -4.26474168e-17, 4.06293718e-16, 173 | 2.76281560e-16, 3.26921030e-16], 174 | [-5.17219625e-16, 2.29608624e-16, -3.11123397e-18, 175 | 1.94303455e-16, -2.79400340e-16, 2.45641928e-16, 176 | 3.90477982e-16, 2.57888073e-16, -3.08179809e-16, 177 | -4.84799075e-16, -4.02802791e-16], 178 | [ 5.04820884e-16, -6.10790554e-16, 5.86895205e-16, 179 | -4.02859774e-16, 3.04278315e-16, -2.12894415e-17, 180 | -3.58539724e-16, -1.56583906e-16, 4.94802637e-16, 181 | 2.63602992e-16, 1.12410081e-16], 182 | [-8.17830928e-16, 6.59761772e-17, -1.96108036e-16, 183 | 7.72307387e-16, 1.20311579e-16, 3.03438805e-17, 184 | 3.63943041e-16, -2.12327864e-16, -4.62399256e-16, 185 | -8.99413171e-17, -9.55089182e-17], 186 | [ 5.55111512e-16, 8.32667268e-17, -2.22044605e-16, 187 | -4.99600361e-16, -2.22044605e-16, -2.49800181e-16, 188 | 8.32667268e-17, 2.77555756e-16, -4.71844785e-16, 189 | 4.16333634e-16, 6.93889390e-16], 190 | [-1.61764706e-01, -1.57142857e-01, -1.52777778e-01, 191 | -1.48648649e-01, -1.44736842e-01, -1.41025641e-01, 192 | -1.37500000e-01, -1.34146341e-01, -1.30952381e-01, 193 | -1.27906977e-01, -1.25000000e-01]]) 194 | 195 | Xm0_a1 = np.ones(8, dtype = Xm.dtype)[:, None] 196 | Xm1_a1 = np.array([[ 1., -2.], 197 | [ 1., -1.08333333], 198 | [ 1., -1.04347826], 199 | [ 1., -1.02941176], 200 | [ 1., -1.02222222], 201 | [ 1., -1.01785714], 202 | [ 1., -1.01492537], 203 | [ 1., -1.01282051]]) 204 | Xm10_a1 = np.array([[ 1.00000000e+00, -1.09090909e+00, 2.48368345e-17, 205 | 4.17212761e-16, -2.00924534e-16, -3.98863242e-16, 206 | 9.92384986e-16, -1.44931966e-15, 6.03318301e-16, 207 | -3.05311332e-16, -9.09090909e-02], 208 | [ 1.00000000e+00, -1.03030303e+00, -1.22247501e-17, 209 | 1.36023073e-15, -4.48031297e-16, -1.35490509e-15, 210 | 1.52774273e-15, -6.93512486e-16, 1.74736602e-15, 211 | -1.17267307e-15, -3.03030303e-02], 212 | [ 1.00000000e+00, -1.01818182e+00, 2.56033011e-15, 213 | -1.13471667e-15, -1.51893003e-15, 2.05757192e-16, 214 | 3.02007915e-15, -1.40321656e-15, -2.57416563e-17, 215 | 1.94982919e-15, -1.81818182e-02], 216 | [ 1.00000000e+00, -1.01298701e+00, 2.44192998e-15, 217 | 5.20720957e-16, -5.48776475e-16, 2.10655840e-15, 218 | -5.80572636e-15, 3.77248850e-15, 1.38878553e-15, 219 | -2.35575448e-15, -1.29870130e-02], 220 | [ 1.00000000e+00, -1.01010101e+00, 1.88495917e-15, 221 | 6.41223658e-16, 1.18580041e-15, -7.72270515e-16, 222 | -2.62575027e-15, 4.40437387e-15, -4.99371930e-15, 223 | -1.64798730e-15, -1.01010101e-02], 224 | [ 1.00000000e+00, -1.00826446e+00, 4.40941636e-15, 225 | -9.91551517e-16, -2.32484424e-15, 6.43021235e-17, 226 | -9.08245602e-16, 1.81345488e-15, 2.49768366e-16, 227 | 2.12850571e-15, -8.26446281e-03], 228 | [ 1.00000000e+00, -1.00699301e+00, -4.33078097e-15, 229 | 6.28478928e-15, -4.81227239e-15, -3.26880638e-15, 230 | 8.58692579e-15, -2.27870777e-15, -1.40252634e-15, 231 | 3.99680289e-15, -6.99300699e-03], 232 | [ 1.00000000e+00, -1.00606061e+00, 3.47394667e-15, 233 | -1.59763597e-15, -5.28108333e-15, 1.07369329e-14, 234 | -5.53240143e-15, -3.20313334e-15, -5.92624612e-16, 235 | 6.94756752e-16, -6.06060606e-03]]) 236 | 237 | # References for prediction error 238 | referr = {0: E0, 1: E1, 5: E5, 10: E10} 239 | 240 | def setUp(self): 241 | self.levinson_func = None 242 | 243 | def test_simpl0(self): 244 | assert_array_almost_equal(self.levinson_func(self.X, 0)[0], self.X0) 245 | 246 | def test_simple1(self): 247 | assert_array_almost_equal(self.levinson_func(self.X, 1)[0], self.X1) 248 | 249 | def test_simple2(self): 250 | assert_array_almost_equal(self.levinson_func(self.X, 5)[0], self.X5) 251 | assert_array_almost_equal(self.levinson_func(self.X, 10)[0], self.X10) 252 | 253 | def test_error_rank1(self): 254 | for order in [0, 1, 5, 10]: 255 | a, e, k = self.levinson_func(self.X, order) 256 | assert_array_almost_equal(self.referr[order], e) 257 | 258 | def test_arg_handling(self): 259 | # Check Order 260 | try: 261 | self.levinson_func(self.X0, 1) 262 | self.fail("levinson func succeed with bad argument !") 263 | except ValueError, e: 264 | assert str(e) == "Order should be <= size-1" 265 | 266 | # Check empty input 267 | try: 268 | self.levinson_func([], 1) 269 | self.fail("levinson func succeed with bad argument !") 270 | except ValueError, e: 271 | assert str(e) == "Cannot operate on empty array !" 272 | 273 | class TestLevinsonPyBackend(_LevinsonCommon): 274 | def setUp(self): 275 | self.levinson_func = py_levinson 276 | 277 | def test_complex(self): 278 | assert_array_almost_equal(self.levinson_func(self.Xc, 1)[0], self.Xc1) 279 | assert_array_almost_equal(self.levinson_func(self.Xc, 5)[0], self.Xc5) 280 | assert_array_almost_equal(self.levinson_func(self.Xc, 10)[0], self.Xc10) 281 | 282 | class TestLevinsonCBackend(_LevinsonCommon): 283 | def setUp(self): 284 | self.levinson_func = c_levinson 285 | 286 | class TestLevinson(_LevinsonCommon): 287 | def setUp(self): 288 | self.levinson_func = levinson 289 | 290 | # References for filter coefficients 291 | self.ref = {} 292 | self.ref[0] = {0: self.Xm0_a0, 1: self.Xm1_a0, 7: self.Xm7_a0} 293 | self.ref[1] = {0: self.Xm0_a1, 1: self.Xm1_a1, 10: self.Xm10_a1} 294 | 295 | def test_axis0(self): 296 | for order in [0, 1, 7]: 297 | a, e, k = self.levinson_func(self.Xm, order, 0) 298 | assert_array_almost_equal(self.ref[0][order], a) 299 | 300 | def test_axis1(self): 301 | for order in [0, 1, 10]: 302 | a, e, k = self.levinson_func(self.Xm, order, 1) 303 | assert_array_almost_equal(self.ref[1][order], a) 304 | 305 | class TestLPCResidual(TestCase): 306 | def test_simple(self): 307 | """Testing LPC residual of one signal.""" 308 | x = np.linspace(0, 10, 11) 309 | r_res = np.array([0., 1., 1.08531469, 1.23776224, 1.39020979, 310 | 1.54265734, 1.6951049, 1.84755245, 2., 2.15244755, 311 | 2.3048951 ]) 312 | 313 | res = lpcres(x, 2) 314 | assert_array_almost_equal(res, r_res) 315 | 316 | def test_r2(self): 317 | """Testing LPC residual of a set of windows.""" 318 | order = 12 319 | x = np.random.randn(10, 24) 320 | 321 | res = lpcres(x, order) 322 | r_res = np.empty(x.shape, x.dtype) 323 | for i in range(10): 324 | r_res[i] = lfilter(lpc(x[i], order)[0], 1., x[i]) 325 | 326 | assert_array_almost_equal(res, r_res) 327 | 328 | res = lpcres(x, order, usefft=False) 329 | assert_array_almost_equal(res, r_res) 330 | 331 | if __name__ == "__main__": 332 | run_module_suite() 333 | -------------------------------------------------------------------------------- /scikits/talkbox/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cournape/talkbox/ee0ec30a6a6d483eb9284f72bdaf26bd99765f80/scikits/talkbox/misc/__init__.py -------------------------------------------------------------------------------- /scikits/talkbox/misc/peak_picking.py: -------------------------------------------------------------------------------- 1 | 2 | def find_peaks(x, neighbours): 3 | peaks = [] 4 | nx = x.size 5 | 6 | assert 2 * neighbours + 1 <= nx 7 | 8 | if nx == 1: 9 | peaks.append(0) 10 | return peaks 11 | elif nx == 2: 12 | if x[0] > x[1]: 13 | peaks.append(0) 14 | else: 15 | peaks.append(1) 16 | return peaks 17 | 18 | # Handle points which have less than neighs samples on their left 19 | for i in range(neighbours): 20 | cur = x[i] 21 | m = x[i+1] 22 | # look at the left of the current position 23 | for j in range(i): 24 | if m < x[j]: 25 | m = x[j] 26 | # look at the right of the current position 27 | for j in range(i+1, i+neighbours): 28 | if m < x[j]: 29 | m = x[j] 30 | 31 | if cur > m: 32 | peaks.append(i) 33 | #assert(pkcnt <= (nx / neighbours + 1)) 34 | 35 | # Handle points which have at least neighs samples on both their left 36 | # and right 37 | for i in range(neighbours, nx - neighbours): 38 | cur = x[i] 39 | m = x[i+1]; 40 | # look at the left 41 | for j in range(i - neighbours, i): 42 | if m < x[j]: 43 | m = x[j] 44 | # look at the right 45 | for j in range(i+1, i+neighbours): 46 | if m < x[j]: 47 | m = x[j] 48 | 49 | if cur > m: 50 | peaks.append(i) 51 | #assert(pkcnt <= (nx / neighbours + 1)) 52 | 53 | # Handle points which have less than neighs samples on their right 54 | for i in range(nx - neighbours, nx): 55 | cur = x[i] 56 | m = x[i-1] 57 | # look at the left 58 | for j in range(i - neighbours, i): 59 | if m < x[j]: 60 | m = x[j] 61 | 62 | # look at the right 63 | for j in range(i+1, nx): 64 | if m < x[j]: 65 | m = x[j] 66 | 67 | if cur > m: 68 | peaks.append(i) 69 | #assert(pkcnt <= (nx / neighbours + 1)) 70 | 71 | return peaks 72 | -------------------------------------------------------------------------------- /scikits/talkbox/misc/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import numpy as np 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration 6 | config = Configuration('misc', parent_package, top_path) 7 | config.add_data_dir('tests') 8 | 9 | return config 10 | 11 | if __name__ == '__main__': 12 | from numpy.distutils.core import setup 13 | setup(**configuration(top_path='').todict()) 14 | -------------------------------------------------------------------------------- /scikits/talkbox/misc/tests/test_find_peaks.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import TestCase, assert_array_equal, \ 3 | assert_array_almost_equal, dec 4 | 5 | from scikits.talkbox.misc.peak_picking import find_peaks 6 | 7 | class TestFindPeaks(TestCase): 8 | def test_simple(self): 9 | x = np.sin(np.linspace(0, 6 * np.pi, 256)) 10 | p = find_peaks(x, 10) 11 | assert_array_equal(p, [21, 106, 191, 255]) 12 | 13 | if __name__ == "__main__": 14 | run_module_suite() 15 | -------------------------------------------------------------------------------- /scikits/talkbox/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import join 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs 6 | config = Configuration('talkbox', parent_package, top_path) 7 | config.add_subpackage('features') 8 | config.add_subpackage('linpred') 9 | config.add_subpackage('spectral') 10 | config.add_subpackage('transforms') 11 | config.add_subpackage('tools') 12 | config.add_subpackage('misc') 13 | return config 14 | 15 | if __name__ == "__main__": 16 | from numpy.distutils.core import setup 17 | config = configuration(top_path='').todict() 18 | setup(**config) 19 | -------------------------------------------------------------------------------- /scikits/talkbox/spectral/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cournape/talkbox/ee0ec30a6a6d483eb9284f72bdaf26bd99765f80/scikits/talkbox/spectral/__init__.py -------------------------------------------------------------------------------- /scikits/talkbox/spectral/basic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.fftpack import fft, ifft 3 | 4 | from scikits.talkbox.linpred import lpc 5 | 6 | def periodogram(x, nfft=None, fs=1): 7 | """Compute the periodogram of the given signal, with the given fft size. 8 | 9 | Parameters 10 | ---------- 11 | x : array-like 12 | input signal 13 | nfft : int 14 | size of the fft to compute the periodogram. If None (default), the 15 | length of the signal is used. if nfft > n, the signal is 0 padded. 16 | fs : float 17 | Sampling rate. By default, is 1 (normalized frequency. e.g. 0.5 is the 18 | Nyquist limit). 19 | 20 | Returns 21 | ------- 22 | pxx : array-like 23 | The psd estimate. 24 | fgrid : array-like 25 | Frequency grid over which the periodogram was estimated. 26 | 27 | Examples 28 | -------- 29 | Generate a signal with two sinusoids, and compute its periodogram: 30 | 31 | >>> fs = 1000 32 | >>> x = np.sin(2 * np.pi * 0.1 * fs * np.linspace(0, 0.5, 0.5*fs)) 33 | >>> x += np.sin(2 * np.pi * 0.2 * fs * np.linspace(0, 0.5, 0.5*fs)) 34 | >>> px, fx = periodogram(x, 512, fs) 35 | 36 | Notes 37 | ----- 38 | Only real signals supported for now. 39 | 40 | Returns the one-sided version of the periodogram. 41 | 42 | Discrepency with matlab: matlab compute the psd in unit of power / radian / 43 | sample, and we compute the psd in unit of power / sample: to get the same 44 | result as matlab, just multiply the result from talkbox by 2pi""" 45 | # TODO: this is basic to the point of being useless: 46 | # - support Daniel smoothing 47 | # - support windowing 48 | # - trend/mean handling 49 | # - one-sided vs two-sided 50 | # - plot 51 | # - support complex input 52 | x = np.atleast_1d(x) 53 | n = x.size 54 | 55 | if x.ndim > 1: 56 | raise ValueError("Only rank 1 input supported for now.") 57 | if not np.isrealobj(x): 58 | raise ValueError("Only real input supported for now.") 59 | if not nfft: 60 | nfft = n 61 | if nfft < n: 62 | raise ValueError("nfft < signal size not supported yet") 63 | 64 | pxx = np.abs(fft(x, nfft)) ** 2 65 | if nfft % 2 == 0: 66 | pn = nfft / 2 + 1 67 | else: 68 | pn = (nfft + 1 )/ 2 69 | 70 | fgrid = np.linspace(0, fs * 0.5, pn) 71 | return pxx[:pn] / (n * fs), fgrid 72 | 73 | def arspec(x, order, nfft=None, fs=1): 74 | """Compute the spectral density using an AR model. 75 | 76 | An AR model of the signal is estimated through the Yule-Walker equations; 77 | the estimated AR coefficient are then used to compute the spectrum, which 78 | can be computed explicitely for AR models. 79 | 80 | Parameters 81 | ---------- 82 | x : array-like 83 | input signal 84 | order : int 85 | Order of the LPC computation. 86 | nfft : int 87 | size of the fft to compute the periodogram. If None (default), the 88 | length of the signal is used. if nfft > n, the signal is 0 padded. 89 | fs : float 90 | Sampling rate. By default, is 1 (normalized frequency. e.g. 0.5 is the 91 | Nyquist limit). 92 | 93 | Returns 94 | ------- 95 | pxx : array-like 96 | The psd estimate. 97 | fgrid : array-like 98 | Frequency grid over which the periodogram was estimated. 99 | """ 100 | 101 | x = np.atleast_1d(x) 102 | n = x.size 103 | 104 | if x.ndim > 1: 105 | raise ValueError("Only rank 1 input supported for now.") 106 | if not np.isrealobj(x): 107 | raise ValueError("Only real input supported for now.") 108 | if not nfft: 109 | nfft = n 110 | if nfft < n: 111 | raise ValueError("nfft < signal size not supported yet") 112 | 113 | a, e, k = lpc(x, order) 114 | 115 | # This is not enough to deal correctly with even/odd size 116 | if nfft % 2 == 0: 117 | pn = nfft / 2 + 1 118 | else: 119 | pn = (nfft + 1 )/ 2 120 | 121 | px = 1 / np.fft.fft(a, nfft)[:pn] 122 | pxx = np.real(np.conj(px) * px) 123 | pxx /= fs / e 124 | fx = np.linspace(0, fs * 0.5, pxx.size) 125 | return pxx, fx 126 | 127 | def taper(n, p=0.1): 128 | """Return a split cosine bell taper (or window) 129 | 130 | Parameters 131 | ---------- 132 | n: int 133 | number of samples of the taper 134 | p: float 135 | proportion of taper (0 <= p <= 1.) 136 | 137 | Note 138 | ---- 139 | p represents the proportion of tapered (or "smoothed") data compared to a 140 | boxcar. 141 | """ 142 | if p > 1. or p < 0: 143 | raise ValueError("taper proportion should be betwen 0 and 1 (was %f)" 144 | % p) 145 | w = np.ones(n) 146 | ntp = np.floor(0.5 * n * p) 147 | w[:ntp] = 0.5 * (1 - np.cos(np.pi * 2 * np.linspace(0, 0.5, ntp))) 148 | w[-ntp:] = 0.5 * (1 - np.cos(np.pi * 2 * np.linspace(0.5, 0, ntp))) 149 | 150 | return w 151 | -------------------------------------------------------------------------------- /scikits/talkbox/spectral/r_examples.R: -------------------------------------------------------------------------------- 1 | # Simplest periodogram: no smoothing (no kernel, no tapping), no detrend. 2 | p = spectrum(lh, plot=FALSE, detrend=FALSE, taper=0.0) 3 | print(p$spec) 4 | -------------------------------------------------------------------------------- /scikits/talkbox/spectral/tests/test_basic.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import numpy as np 4 | from numpy.testing import assert_array_almost_equal 5 | 6 | from scikits.talkbox.spectral.basic import periodogram 7 | 8 | lh = np.array([2.4, 2.4, 2.4, 2.2, 2.1, 1.5, 2.3, 2.3, 2.5, 2.0, 1.9, 1.7, 9 | 2.2, 1.8, 3.2, 3.2, 2.7, 2.2, 2.2, 1.9, 1.9, 1.8, 2.7, 3.0, 2.3, 2.0, 2.0, 2.9, 10 | 2.9, 2.7, 2.7, 2.3, 2.6, 2.4, 1.8, 1.7, 1.5, 1.4, 2.1, 3.3, 3.5, 3.5, 3.1, 2.6, 11 | 2.1, 3.4, 3.0, 2.9]) 12 | 13 | # Returned by p = spectrum(lh, plot=FALSE, detrend=FALSE, taper=0.0) 14 | # XXX: R drops the first item (DC component) of the estimated spectrogram 15 | lh_spec_raw = np.array([0.32650971, 0.79865114, 1.25684523, 0.66284366, 16 | 0.13803913, 1.51075717, 0.23526771, 0.66520833, 0.28393413, 0.09545270, 17 | 0.24710226, 0.18541667, 0.02353390, 0.07176963, 0.05049468, 0.11312500, 18 | 0.02028320, 0.04174283, 0.09056918, 0.01548967, 0.02039262, 0.11912653, 19 | 0.16702824, 0.02083333]) 20 | 21 | class TestSpecgram(TestCase): 22 | def test_raw(self): 23 | sp = periodogram(lh)[0] 24 | assert_array_almost_equal(sp[1:], lh_spec_raw) 25 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | 3 | import correlations 4 | from correlations import * 5 | __all__ += correlations.__all__ 6 | 7 | import cffilter 8 | from cffilter import cslfilter as slfilter 9 | __all__ += ['slfilter'] 10 | 11 | from segmentaxis import segment_axis 12 | __all__ += ['segment_axis'] 13 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/correlations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.fftpack import fft, ifft 3 | 4 | __all__ = ['nextpow2', 'acorr'] 5 | 6 | def nextpow2(n): 7 | """Return the next power of 2 such as 2^p >= n. 8 | 9 | Notes 10 | ----- 11 | Infinite and nan are left untouched, negative values are not allowed.""" 12 | if np.any(n < 0): 13 | raise ValueError("n should be > 0") 14 | 15 | if np.isscalar(n): 16 | f, p = np.frexp(n) 17 | if f == 0.5: 18 | return p-1 19 | elif np.isfinite(f): 20 | return p 21 | else: 22 | return f 23 | else: 24 | f, p = np.frexp(n) 25 | res = f 26 | bet = np.isfinite(f) 27 | exa = (f == 0.5) 28 | res[bet] = p[bet] 29 | res[exa] = p[exa] - 1 30 | return res 31 | 32 | def _acorr_last_axis(x, nfft, maxlag, onesided=False, scale='none'): 33 | a = np.real(ifft(np.abs(fft(x, n=nfft) ** 2))) 34 | if onesided: 35 | b = a[..., :maxlag] 36 | else: 37 | b = np.concatenate([a[..., nfft-maxlag+1:nfft], 38 | a[..., :maxlag]], axis=-1) 39 | #print b, a[..., 0][..., np.newaxis], b / a[..., 0][..., np.newaxis] 40 | if scale == 'coeff': 41 | return b / a[..., 0][..., np.newaxis] 42 | else: 43 | return b 44 | 45 | def acorr(x, axis=-1, onesided=False, scale='none'): 46 | """Compute autocorrelation of x along given axis. 47 | 48 | Parameters 49 | ---------- 50 | x : array-like 51 | signal to correlate. 52 | axis : int 53 | axis along which autocorrelation is computed. 54 | onesided: bool, optional 55 | if True, only returns the right side of the autocorrelation. 56 | scale: {'none', 'coeff'} 57 | scaling mode. If 'coeff', the correlation is normalized such as the 58 | 0-lag is equal to 1. 59 | 60 | Notes 61 | ----- 62 | Use fft for computation: is more efficient than direct computation for 63 | relatively large n. 64 | """ 65 | if not np.isrealobj(x): 66 | raise ValueError("Complex input not supported yet") 67 | if not scale in ['none', 'coeff']: 68 | raise ValueError("scale mode %s not understood" % scale) 69 | 70 | maxlag = x.shape[axis] 71 | nfft = 2 ** nextpow2(2 * maxlag - 1) 72 | 73 | if axis != -1: 74 | x = np.swapaxes(x, -1, axis) 75 | a = _acorr_last_axis(x, nfft, maxlag, onesided, scale) 76 | if axis != -1: 77 | a = np.swapaxes(a, -1, axis) 78 | return a 79 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/ffilter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import lfilter 3 | 4 | def slfilter(b, a, x): 5 | """Filter a set of frames and filter coefficients. More precisely, given 6 | rank 2 arrays for coefficients and input, this computes: 7 | 8 | for i in range(x.shape[0]): 9 | y[i] = lfilter(b[i], a[i], x[i]) 10 | 11 | This is mostly useful for processing on a set of windows with variable 12 | filters, e.g. to compute LPC residual from a signal chopped into a set of 13 | windows. 14 | 15 | Parameters 16 | ---------- 17 | b: array 18 | recursive coefficients 19 | a: array 20 | non-recursive coefficients 21 | x: array 22 | signal to filter 23 | 24 | Note 25 | ---- 26 | 27 | This is a specialized function, and does not handle initial conditions, 28 | rank > 2 nor arbitrary axis handling.""" 29 | 30 | if not x.ndim == 2: 31 | raise ValueError("Only input of rank 2 support") 32 | 33 | if not b.ndim == 2: 34 | raise ValueError("Only b of rank 2 support") 35 | 36 | if not a.ndim == 2: 37 | raise ValueError("Only a of rank 2 support") 38 | 39 | nfr = a.shape[0] 40 | if not nfr == b.shape[0]: 41 | raise ValueError("Number of filters should be the same") 42 | 43 | if not nfr == x.shape[0]: 44 | raise ValueError, \ 45 | "Number of filters and number of frames should be the same" 46 | 47 | y = np.empty((x.shape[0], x.shape[1]), x.dtype) 48 | 49 | for i in range(nfr): 50 | y[i] = lfilter(b[i], a[i], x[i]) 51 | 52 | return y 53 | 54 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/preprocess.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | __all__ = ["demean"] 4 | 5 | def demean(x, axis=-1): 6 | return x - np.mean(x,axis) 7 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/segmentaxis.py: -------------------------------------------------------------------------------- 1 | """sgementaxis code. 2 | 3 | This code has been implemented by Anne Archibald, and has been discussed on the 4 | ML.""" 5 | 6 | import numpy as np 7 | import warnings 8 | 9 | def segment_axis(a, length, overlap=0, axis=None, end='cut', endvalue=0): 10 | """Generate a new array that chops the given array along the given axis 11 | into overlapping frames. 12 | 13 | example: 14 | >>> segment_axis(arange(10), 4, 2) 15 | array([[0, 1, 2, 3], 16 | [2, 3, 4, 5], 17 | [4, 5, 6, 7], 18 | [6, 7, 8, 9]]) 19 | 20 | arguments: 21 | a The array to segment 22 | length The length of each frame 23 | overlap The number of array elements by which the frames should overlap 24 | axis The axis to operate on; if None, act on the flattened array 25 | end What to do with the last frame, if the array is not evenly 26 | divisible into pieces. Options are: 27 | 28 | 'cut' Simply discard the extra values 29 | 'wrap' Copy values from the beginning of the array 30 | 'pad' Pad with a constant value 31 | 32 | endvalue The value to use for end='pad' 33 | 34 | The array is not copied unless necessary (either because it is unevenly 35 | strided and being flattened or because end is set to 'pad' or 'wrap'). 36 | """ 37 | 38 | if axis is None: 39 | a = np.ravel(a) # may copy 40 | axis = 0 41 | 42 | l = a.shape[axis] 43 | 44 | if overlap >= length: 45 | raise ValueError, "frames cannot overlap by more than 100%" 46 | if overlap < 0 or length <= 0: 47 | raise ValueError, "overlap must be nonnegative and length must "\ 48 | "be positive" 49 | 50 | if l < length or (l-length) % (length-overlap): 51 | if l>length: 52 | roundup = length + (1+(l-length)//(length-overlap))*(length-overlap) 53 | rounddown = length + ((l-length)//(length-overlap))*(length-overlap) 54 | else: 55 | roundup = length 56 | rounddown = 0 57 | assert rounddown < l < roundup 58 | assert roundup == rounddown + (length-overlap) \ 59 | or (roundup == length and rounddown == 0) 60 | a = a.swapaxes(-1,axis) 61 | 62 | if end == 'cut': 63 | a = a[..., :rounddown] 64 | elif end in ['pad','wrap']: # copying will be necessary 65 | s = list(a.shape) 66 | s[-1] = roundup 67 | b = np.empty(s,dtype=a.dtype) 68 | b[..., :l] = a 69 | if end == 'pad': 70 | b[..., l:] = endvalue 71 | elif end == 'wrap': 72 | b[..., l:] = a[..., :roundup-l] 73 | a = b 74 | 75 | a = a.swapaxes(-1,axis) 76 | 77 | 78 | l = a.shape[axis] 79 | if l == 0: 80 | raise ValueError, \ 81 | "Not enough data points to segment array in 'cut' mode; "\ 82 | "try 'pad' or 'wrap'" 83 | assert l >= length 84 | assert (l-length) % (length-overlap) == 0 85 | n = 1 + (l-length) // (length-overlap) 86 | s = a.strides[axis] 87 | newshape = a.shape[:axis] + (n,length) + a.shape[axis+1:] 88 | newstrides = a.strides[:axis] + ((length-overlap)*s,s) + a.strides[axis+1:] 89 | 90 | try: 91 | return np.ndarray.__new__(np.ndarray, strides=newstrides, 92 | shape=newshape, buffer=a, dtype=a.dtype) 93 | except TypeError: 94 | warnings.warn("Problem with ndarray creation forces copy.") 95 | a = a.copy() 96 | # Shape doesn't change but strides does 97 | newstrides = a.strides[:axis] + ((length-overlap)*s,s) \ 98 | + a.strides[axis+1:] 99 | return np.ndarray.__new__(np.ndarray, strides=newstrides, 100 | shape=newshape, buffer=a, dtype=a.dtype) 101 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import numpy as np 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration 6 | config = Configuration('tools', parent_package, top_path) 7 | config.add_data_dir('tests') 8 | config.add_extension('cffilter', ['src/cffilter.c']) 9 | config.add_extension('cacorr', ['src/cacorr.c']) 10 | 11 | return config 12 | 13 | if __name__ == '__main__': 14 | from numpy.distutils.core import setup 15 | setup(**configuration(top_path='').todict()) 16 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/src/SConstruct: -------------------------------------------------------------------------------- 1 | from numpy.distutils.misc_util import get_numpy_include_dirs 2 | 3 | env = Environment() 4 | DefaultEnvironment(tools = []) 5 | 6 | env.Append(CPPPATH = ["/usr/include/python2.5"] + get_numpy_include_dirs()) 7 | env.Append(CFLAGS = ["-Wall", "-Wextra", "-W"]) 8 | env.Append(CFLAGS = ["-O3", "-funroll-loops"]) 9 | 10 | env["LDMODULESUFFIX"] = ".so" 11 | env["LDMODULEPREFIX"] = "" 12 | env.Append(LINKFLAGS="-undefined dynamic_lookup") 13 | env.LoadableModule("cffilter.c") 14 | env.LoadableModule("cacorr.c") 15 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/src/acorr.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def acorr(x, axis=-1, maxlag=None, onesided=False): 4 | if maxlag is None: 5 | maxlag = x.shape[axis] 6 | 7 | def tmpcorr(x): 8 | return _acorr1(x, maxlag, onesided) 9 | 10 | return np.apply_along_axis(tmpcorr, axis, x) 11 | 12 | def _acorr1(x, maxlag, onesided): 13 | """Autocorrelation of a rank one array. 14 | 15 | Pure python, without vectorization: will be used for reference to the 16 | cython version""" 17 | if onesided: 18 | y = np.zeros(maxlag, x.dtype) 19 | for i in range(y.size): 20 | for j in range(x.size - i): 21 | y[i] += x[j] * x[i+j] 22 | else: 23 | y = np.zeros(2 * maxlag - 1, x.dtype) 24 | for i in range(maxlag): 25 | for j in range(x.size - i): 26 | y[i+maxlag-1] += x[j] * x[i+j] 27 | for i in range(0, maxlag-1): 28 | y[i] = y[-i-1] 29 | 30 | return y 31 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/src/cacorr.pyx: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | cimport numpy as c_np 3 | cimport stdlib 4 | 5 | def acorr(c_np.ndarray x, maxlag=None, onesided=False, axis=-1): 6 | """Cython version of autocorrelation, direct implementation. This can be 7 | faster than FFT for small size or for maxlag << x.shape[axis].""" 8 | cdef double *raw_x, *raw_y 9 | cdef int raw_maxlag, nfr, raw_onesided, nx, ny, i 10 | cdef c_np.ndarray[double, ndim=2] tx 11 | cdef c_np.ndarray[double, ndim=2] ty 12 | 13 | if not x.dtype == np.float64: 14 | raise ValueError("Only float64 supported for now") 15 | 16 | if not x.ndim == 2: 17 | raise ValueError("Rank != 2 not supported yet") 18 | 19 | axis = axis % x.ndim 20 | if not axis == 1: 21 | raise ValueError("Axis != 1 not supported yet") 22 | 23 | tx = np.ascontiguousarray(x) 24 | 25 | if maxlag is None: 26 | raw_maxlag = x.shape[axis] - 1 27 | else: 28 | raw_maxlag = maxlag 29 | 30 | nfr = tx.shape[0] 31 | nx = tx.shape[axis] 32 | if onesided: 33 | ny = raw_maxlag+1 34 | ty = np.zeros((nfr, ny), x.dtype) 35 | raw_onesided = 1 36 | else: 37 | ny = 2*raw_maxlag+1 38 | ty = np.zeros((nfr, ny), x.dtype) 39 | raw_onesided = 0 40 | 41 | raw_x = tx.data 42 | raw_y = ty.data 43 | 44 | for i in range(nfr): 45 | acorr_double(raw_x, nx, raw_maxlag, raw_onesided, raw_y) 46 | raw_x += nx 47 | raw_y += ny 48 | 49 | return ty 50 | 51 | # y values are assumed to be set to 0 52 | cdef int acorr_double(double* x, int nx, int maxlag, int onesided, double* y): 53 | cdef int i, j, ni, nf 54 | cdef double gain, acc 55 | 56 | if onesided: 57 | for i in range(maxlag+1): 58 | for j in range(nx-i): 59 | y[i] += x[j] * x[i+j] 60 | else: 61 | for i in range(maxlag+1): 62 | for j in range(nx-i): 63 | y[i+maxlag] += x[j] * x[i+j] 64 | for i in range(maxlag): 65 | y[i] = y[2*maxlag-i] 66 | 67 | return 0 68 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/src/cffilter.pyx: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | cimport numpy as c_np 3 | cimport stdlib 4 | 5 | def cslfilter(c_np.ndarray b, c_np.ndarray a, c_np.ndarray x): 6 | """Fast version of slfilter for a set of frames and filter coefficients. 7 | More precisely, given rank 2 arrays for coefficients and input, this 8 | computes: 9 | 10 | for i in range(x.shape[0]): 11 | y[i] = lfilter(b[i], a[i], x[i]) 12 | 13 | This is mostly useful for processing on a set of windows with variable 14 | filters, e.g. to compute LPC residual from a signal chopped into a set of 15 | windows. 16 | 17 | Parameters 18 | ---------- 19 | b: array 20 | recursive coefficients 21 | a: array 22 | non-recursive coefficients 23 | x: array 24 | signal to filter 25 | 26 | Note 27 | ---- 28 | 29 | This is a specialized function, and does not handle other types than 30 | double, nor initial conditions.""" 31 | 32 | cdef int na, nb, nfr, i, nx 33 | cdef double *raw_x, *raw_a, *raw_b, *raw_y 34 | cdef c_np.ndarray[double, ndim=2] tb 35 | cdef c_np.ndarray[double, ndim=2] ta 36 | cdef c_np.ndarray[double, ndim=2] tx 37 | cdef c_np.ndarray[double, ndim=2] ty 38 | 39 | dt = np.common_type(a, b, x) 40 | 41 | if not dt == np.float64: 42 | raise ValueError("Only float64 supported for now") 43 | 44 | if not x.ndim == 2: 45 | raise ValueError("Only input of rank 2 support") 46 | 47 | if not b.ndim == 2: 48 | raise ValueError("Only b of rank 2 support") 49 | 50 | if not a.ndim == 2: 51 | raise ValueError("Only a of rank 2 support") 52 | 53 | nfr = a.shape[0] 54 | if not nfr == b.shape[0]: 55 | raise ValueError("Number of filters should be the same") 56 | 57 | if not nfr == x.shape[0]: 58 | raise ValueError, \ 59 | "Number of filters and number of frames should be the same" 60 | 61 | tx = np.ascontiguousarray(x, dtype=dt) 62 | ty = np.ones((x.shape[0], x.shape[1]), dt) 63 | 64 | na = a.shape[1] 65 | nb = b.shape[1] 66 | nx = x.shape[1] 67 | 68 | ta = np.ascontiguousarray(np.copy(a), dtype=dt) 69 | tb = np.ascontiguousarray(np.copy(b), dtype=dt) 70 | 71 | raw_x = tx.data 72 | raw_b = tb.data 73 | raw_a = ta.data 74 | raw_y = ty.data 75 | 76 | for i in range(nfr): 77 | filter_double(raw_b, nb, raw_a, na, raw_x, nx, raw_y) 78 | raw_b += nb 79 | raw_a += na 80 | raw_x += nx 81 | raw_y += nx 82 | 83 | return ty 84 | 85 | # a and b are modified in place 86 | cdef int filter_double(double* b, int nb, double* a, int na, double* x, int nx, double* y): 87 | 88 | cdef int i, j, ni, nf 89 | cdef double gain, acc 90 | 91 | if na > nb: 92 | nf = na 93 | else: 94 | nf = nb 95 | 96 | gain = a[0] 97 | if gain == 0: 98 | return -1 99 | 100 | if gain != 1: 101 | for i in range(na): 102 | a[i] /= gain 103 | for i in range(nb): 104 | b[i] /= gain 105 | 106 | for i in range(nf): 107 | acc = 0 108 | acc += x[i] * b[0] 109 | if nb < i+1: 110 | ni = nb 111 | else: 112 | ni = i+1 113 | for j in range(1, ni): 114 | acc += x[i-j] * b[j] 115 | 116 | if na < i+1: 117 | ni = na 118 | else: 119 | ni = i+1 120 | for j in range(1, ni): 121 | acc -= y[i-j] * a[j] 122 | 123 | y[i] = acc 124 | 125 | for i in range(nf, nx): 126 | acc = 0 127 | acc += x[i] * b[0] 128 | for j in range(1, nb): 129 | acc += x[i-j] * b[j] 130 | for j in range(1, na): 131 | acc -= y[i-j] * a[j] 132 | 133 | y[i] = acc 134 | 135 | return 0 136 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/src/test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import lfilter 3 | 4 | from scikits.talkbox.tools.correlations import acorr 5 | 6 | from cffilter import cslfilter as lfilter2 7 | from cacorr import acorr as cacorr 8 | 9 | # def f1(b, a, x): 10 | # y = np.empty(x.shape, x.dtype) 11 | # 12 | # for i in range(y.shape[0]): 13 | # y[i] = lfilter(b[i], a[i], x[i]) 14 | # return y 15 | # 16 | # def f2(b, a, x): 17 | # return lfilter2(b, a, x) 18 | # 19 | # nframes = 3600 20 | # a = np.random.randn(nframes, 1) 21 | # #a = np.array([[1], [-1]]) 22 | # b = np.random.randn(nframes, 24) 23 | # #b = np.array([[1, 1], [1, 1]]) 24 | # x = np.random.randn(nframes, 256) 25 | # #x = np.linspace(0, 19, 20).reshape((2, 10)) 26 | # 27 | # y = f1(b, a, x) 28 | # yr = f2(b, a, x) 29 | # np.testing.assert_array_almost_equal(y, yr) 30 | 31 | x = np.random.randn(1, 5) 32 | 33 | maxlag = 1 34 | onesided = False 35 | axis = 1 36 | nx = x.shape[1] 37 | y = cacorr(x, maxlag, onesided, axis) 38 | r_y = acorr(x, axis, onesided) 39 | 40 | #print y 41 | #print r_y 42 | np.testing.assert_array_almost_equal(y, r_y[:,nx-maxlag-1:nx+maxlag]) 43 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/tests/test_correlations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import TestCase, assert_array_equal, \ 3 | assert_array_almost_equal, dec 4 | 5 | from scikits.talkbox.tools.correlations import nextpow2, acorr 6 | from scikits.talkbox.tools.cacorr import acorr as cacorr 7 | 8 | class TestNextpow2(TestCase): 9 | X = np.array([0, 1, 2, 3, 4, 6, 8, 15, 16, 17, 32, np.nan, np.infty]) 10 | Y = np.array([0., 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, np.nan, np.infty]) 11 | def test_simple(self): 12 | assert nextpow2(0) == 0 13 | assert nextpow2(1) == 0 14 | assert nextpow2(2) == 1 15 | assert nextpow2(3) == 2 16 | assert nextpow2(4) == 2 17 | 18 | def test_vector(self): 19 | assert_array_equal(nextpow2(self.X), self.Y) 20 | 21 | class _TestCorrCommon(TestCase): 22 | X = np.linspace(1, 11, 11) 23 | Y = np.array([11.0000, 32.0000, 62.0000, 100.0000, 145.0000, 196.0000, 24 | 252.0000, 312.0000, 375.0000, 440.0000, 506.0000, 440.0000, 25 | 375.0000, 312.0000, 252.0000, 196.0000, 145.0000, 100.0000, 26 | 62.0000, 32.0000 , 11.0000]) 27 | Yc = np.array([ 0.02173913043478, 0.06324110671937, 0.12252964426877, 28 | 0.19762845849802, 0.28656126482213, 0.38735177865613, 0.49802371541502, 29 | 0.61660079051383, 0.74110671936759, 0.86956521739130, 1.00000000000000, 30 | 0.86956521739130, 0.74110671936759, 0.61660079051383, 0.49802371541502, 31 | 0.38735177865613, 0.28656126482213, 0.19762845849802, 0.12252964426877, 32 | 0.06324110671937, 0.02173913043478,]) 33 | 34 | Xm = np.linspace(1, 22, 22).reshape(2, 11) 35 | Ym = np.array([[11., 32., 62., 100., 145., 196., 252., 312., 375., 36 | 440., 506., 440., 375., 312., 252., 196., 145., 100., 37 | 62., 32., 11.], 38 | [264., 538., 821., 1112., 1410., 1714., 2023., 2336., 39 | 2652., 2970., 3289., 2970., 2652., 2336., 2023., 1714., 40 | 1410., 1112., 821., 538., 264.]]) 41 | 42 | def test_simple(self): 43 | """Test autocorrelation for a rank 1 array.""" 44 | a = self.acorr(self.X) 45 | assert_array_almost_equal(a, self.Y) 46 | 47 | def test_axis0(self): 48 | """Test autocorrelation along default axis.""" 49 | a = self.acorr(self.Xm) 50 | assert_array_almost_equal(a, self.Ym) 51 | 52 | def test_axis1(self): 53 | """Test autocorrelation along axis 0.""" 54 | a = self.acorr(self.Xm.T, axis=0) 55 | assert_array_almost_equal(a, self.Ym.T) 56 | 57 | def test_normalization(self): 58 | a = self.acorr(self.X, scale='coeff') 59 | assert_array_almost_equal(a, self.Yc) 60 | 61 | def test_normalization_onesided(self): 62 | a = self.acorr(self.X, scale='coeff', onesided=True) 63 | assert_array_almost_equal(a, self.Yc[self.X.size-1:]) 64 | 65 | def test_normalization_axis1(self): 66 | x = np.random.randn(5, 25) 67 | a = np.zeros((5, 49)) 68 | for i in range(5): 69 | a[i] = self.acorr(x[i], scale='coeff') 70 | b = self.acorr(x, scale='coeff', axis=-1) 71 | 72 | assert_array_almost_equal(b, a) 73 | 74 | def test_normalization_axis0(self): 75 | x = np.random.randn(5, 25) 76 | a = np.zeros((9, 25)) 77 | for i in range(25): 78 | a[:, i] = self.acorr(x[:, i], scale='coeff', axis=0) 79 | b = self.acorr(x, scale='coeff', axis=0) 80 | 81 | assert_array_almost_equal(b, a) 82 | 83 | def test_normalization_onesided_axis1(self): 84 | x = np.random.randn(5, 25) 85 | a = np.zeros((5, 25)) 86 | for i in range(5): 87 | a[i] = self.acorr(x[i], scale='coeff', onesided=True) 88 | b = self.acorr(x, scale='coeff', axis=-1, onesided=True) 89 | 90 | assert_array_almost_equal(b, a) 91 | 92 | def test_normalization_onesided_axis0(self): 93 | x = np.random.randn(5, 25) 94 | a = np.zeros((5, 25)) 95 | for i in range(25): 96 | a[:, i] = self.acorr(x[:, i], scale='coeff', axis=0, onesided=True) 97 | b = self.acorr(x, scale='coeff', axis=0, onesided=True) 98 | 99 | assert_array_almost_equal(b, a) 100 | 101 | class TestAcorr(_TestCorrCommon): 102 | def setUp(self): 103 | self.acorr = acorr 104 | 105 | #class TestCythonAcorr(_TestCorrCommon): 106 | # def setUp(self): 107 | # self.acorr = cacorr 108 | # self.X = self.X[np.newaxis, :] 109 | # self.Y = self.Y[np.newaxis, :] 110 | # 111 | # @dec.skipif(True, "Arbitrary axis not suppported yet in cython version") 112 | # def test_axis1(self): 113 | # pass 114 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/tests/test_ffilter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import TestCase, assert_array_almost_equal 3 | 4 | from scipy.signal import lfilter 5 | 6 | from scikits.talkbox.tools.ffilter import slfilter 7 | 8 | class TestFfilter(TestCase): 9 | def setUp(self): 10 | self.func = slfilter 11 | 12 | def test1(self): 13 | x = np.random.randn(2, 10) 14 | b = np.random.randn(2, 2) 15 | a = np.random.randn(2, 3) 16 | 17 | y = self.func(b, a, x) 18 | for i in range(x.shape[0]): 19 | yr = lfilter(b[i], a[i], x[i]) 20 | assert_array_almost_equal(y[i], yr) 21 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/tests/test_preprocess.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import * 3 | 4 | from scikits.talkbox.tools.preprocess import demean 5 | 6 | class TestDeman(TestCase): 7 | def test1(self): 8 | a = np.array([1, 3, 1, 3]) 9 | 10 | assert_array_equal(demean(a), 11 | np.array([-1, 1, -1, 1])) 12 | 13 | def test2(self): 14 | a = np.array([[1, 3], [1, 3]]) 15 | 16 | assert_array_equal(demean(a), 17 | np.array([[-1, 1], [-1, 1]])) 18 | 19 | assert_array_equal(demean(a,0), 20 | np.zeros((2, 2), a.dtype)) 21 | 22 | -------------------------------------------------------------------------------- /scikits/talkbox/tools/tests/test_segmentaxis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import TestCase, assert_array_almost_equal, assert_equal 3 | 4 | from scikits.talkbox.tools.segmentaxis import segment_axis 5 | 6 | class TestSegmentAxis(TestCase): 7 | def test1(self): 8 | x = np.linspace(0, 9, 10) 9 | 10 | y = segment_axis(x, 4) 11 | assert_array_almost_equal(y, np.array([[0, 1, 2, 3], [4, 5, 6, 7]])) 12 | 13 | y = segment_axis(x, 4, 2) 14 | assert_array_almost_equal(y, np.array([[0, 1, 2, 3], [2, 3, 4, 5], 15 | [4, 5, 6, 7], [6, 7, 8, 9]])) 16 | 17 | y = segment_axis(x, 3, 2) 18 | assert_array_almost_equal(y, np.array([[0, 1, 2], [1, 2, 3], 19 | [2, 3, 4], [3, 4, 5], 20 | [4, 5, 6], [5, 6, 7], 21 | [6, 7, 8], [7, 8, 9]])) 22 | 23 | y = segment_axis(x, 4, 3) 24 | assert_array_almost_equal(y, np.array([[0, 1, 2, 3], [1, 2, 3, 4], 25 | [2, 3, 4, 5], [3, 4, 5, 6], 26 | [4, 5, 6, 7], [5, 6, 7, 8], 27 | [6, 7, 8, 9]])) 28 | 29 | 30 | def test_simple(self): 31 | assert_equal(segment_axis(np.arange(6),length=3,overlap=0), 32 | np.array([[0,1,2],[3,4,5]])) 33 | 34 | assert_equal(segment_axis(np.arange(7),length=3,overlap=1), 35 | np.array([[0,1,2],[2,3,4],[4,5,6]])) 36 | 37 | assert_equal(segment_axis(np.arange(7),length=3,overlap=2), 38 | np.array([[0,1,2],[1,2,3],[2,3,4],[3,4,5],[4,5,6]])) 39 | 40 | def test_error_checking(self): 41 | self.assertRaises(ValueError, 42 | lambda: segment_axis(np.arange(7),length=3,overlap=-1)) 43 | self.assertRaises(ValueError, 44 | lambda: segment_axis(np.arange(7),length=0,overlap=0)) 45 | self.assertRaises(ValueError, 46 | lambda: segment_axis(np.arange(7),length=3,overlap=3)) 47 | self.assertRaises(ValueError, 48 | lambda: segment_axis(np.arange(7),length=8,overlap=3)) 49 | 50 | def test_ending(self): 51 | assert_equal(segment_axis(np.arange(6),length=3,overlap=1,end='cut'), 52 | np.array([[0,1,2],[2,3,4]])) 53 | assert_equal(segment_axis(np.arange(6),length=3,overlap=1,end='wrap'), 54 | np.array([[0,1,2],[2,3,4],[4,5,0]])) 55 | assert_equal(segment_axis(np.arange(6),length=3,overlap=1,end='pad',endvalue=-17), 56 | np.array([[0,1,2],[2,3,4],[4,5,-17]])) 57 | 58 | def test_multidimensional(self): 59 | 60 | assert_equal(segment_axis(np.ones((2,3,4,5,6)), axis=3, length=3, 61 | overlap=1).shape, (2,3,4,2,3,6)) 62 | 63 | assert_equal(segment_axis(np.ones((2,5,4,3,6)).swapaxes(1,3), 64 | axis=3, length=3, overlap=1).shape, 65 | (2,3,4,2,3,6)) 66 | 67 | assert_equal(segment_axis(np.ones((2,3,4,5,6)), axis=2, length=3, 68 | overlap=1, end='cut').shape, 69 | (2,3,1,3,5,6)) 70 | 71 | assert_equal(segment_axis( 72 | np.ones((2,3,4,5,6)), axis=2, length=3, overlap=1, end='wrap').shape, 73 | (2,3,2,3,5,6)) 74 | 75 | assert_equal(segment_axis( 76 | np.ones((2,3,4,5,6)), axis=2, length=3, overlap=1, end='pad').shape, 77 | (2,3,2,3,5,6)) 78 | -------------------------------------------------------------------------------- /scikits/talkbox/transforms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cournape/talkbox/ee0ec30a6a6d483eb9284f72bdaf26bd99765f80/scikits/talkbox/transforms/__init__.py -------------------------------------------------------------------------------- /scikits/talkbox/transforms/dct.py: -------------------------------------------------------------------------------- 1 | """Module implementing various DCTs.""" 2 | 3 | # TODO: 4 | # - make it work along an axis 5 | # - implement dct I, II, III and IV 6 | # - implement mdct 7 | # - 2d version ? 8 | 9 | # Would be nice but a lot of work 10 | # - C implementation 11 | import numpy as np 12 | from scipy.fftpack import fft 13 | 14 | def dctii(x): 15 | """Compute a Discrete Cosine Transform, type II. 16 | 17 | The DCT type II is defined as: 18 | 19 | \forall u \in 0...N-1, 20 | dct(u) = a(u) sum_{i=0}^{N-1}{f(i)cos((i + 0.5)\pi u} 21 | 22 | Where a(0) = sqrt(1/(4N)), a(u) = sqrt(1/(2N)) for u > 0 23 | 24 | Parameters 25 | ========== 26 | x : array-like 27 | input signal 28 | 29 | Returns 30 | ======= 31 | y : array-like 32 | DCT-II 33 | 34 | Note 35 | ==== 36 | Use fft. 37 | """ 38 | if not np.isrealobj(x): 39 | raise ValueError("Complex input not supported") 40 | n = x.size 41 | y = np.zeros(n * 4, x.dtype) 42 | y[1:2*n:2] = x 43 | y[2*n+1::2] = x[-1::-1] 44 | y = np.real(fft(y))[:n] 45 | y[0] *= np.sqrt(.25 / n) 46 | y[1:] *= np.sqrt(.5 / n) 47 | return y 48 | 49 | if __name__ == "__main__": 50 | from dct_ref import direct_dctii_2 51 | a = np.linspace(0, 10, 11) 52 | print direct_dctii_2(a) 53 | print dctii(a) 54 | -------------------------------------------------------------------------------- /scikits/talkbox/transforms/dct_ref.py: -------------------------------------------------------------------------------- 1 | """Module implementing various DCTs. 2 | 3 | This is the reference module, implementing DCT in a direct fashion, without 4 | fft""" 5 | 6 | import numpy as np 7 | 8 | # Definition of DCT in 1d (II type) 9 | # dct(u) = a(u) \sum_{i=0}^{N-1}{f(x)cos(\pi(x + 0.5)u} 10 | 11 | def direct_dctii(x): 12 | """Direct implementation (O(n^2)) of dct II. 13 | 14 | Notes 15 | ----- 16 | 17 | Use it as a reference only, it is not suitable for any real computation.""" 18 | n = x.size 19 | a = np.empty((n, n), dtype = x.dtype) 20 | for i in xrange(n): 21 | for j in xrange(n): 22 | a[i, j] = x[j] * np.cos(np.pi * (0.5 + j) * i / n) 23 | 24 | a[0] *= np.sqrt(1. / n) 25 | a[1:] *= np.sqrt(2. / n) 26 | 27 | return a.sum(axis = 1) 28 | 29 | def direct_dctii_2(x): 30 | """Direct implementation (O(n^2)) of dct.""" 31 | # We are a bit smarter here by computing the coefficient matrix directly, 32 | # but still O(N^2) 33 | n = x.size 34 | 35 | a = np.cos(np.pi / n * np.linspace(0, n - 1, n)[:, None] 36 | * np.linspace(0.5, 0.5 + n - 1, n)[None, :]) 37 | a *= x 38 | a[0] *= np.sqrt(1. / n) 39 | a[1:] *= np.sqrt(2. / n) 40 | 41 | return a.sum(axis = 1) 42 | 43 | if __name__ == "__main__": 44 | a = np.linspace(0, 10, 11) 45 | print direct_dctii_2(a) 46 | a = np.linspace(0, 10, 11) 47 | print direct_dctii_2(a) 48 | -------------------------------------------------------------------------------- /scikits/talkbox/transforms/setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | import numpy as np 3 | 4 | def configuration(parent_package='', top_path=None): 5 | from numpy.distutils.misc_util import Configuration 6 | config = Configuration('transforms', parent_package, top_path) 7 | config.add_data_dir('tests') 8 | 9 | return config 10 | 11 | if __name__ == '__main__': 12 | from numpy.distutils.core import setup 13 | setup(**configuration(top_path='').todict()) 14 | -------------------------------------------------------------------------------- /scikits/talkbox/transforms/tests/test_dct.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import TestCase, assert_array_almost_equal 3 | 4 | from scikits.talkbox.transforms.dct_ref import direct_dctii, direct_dctii_2 5 | from scikits.talkbox.transforms.dct import dctii 6 | 7 | class TestDCTTypeII(TestCase): 8 | Y0 = np.array([16.58312395177700, -10.41945851392763, 0., 9 | -1.12380254641222, -0., -0.37572376864581, -0., -0.16287097213722, 10 | 0., -0.06524422831323, 0.]) 11 | 12 | Y1 = np.array([15.81138830084189, -10.02761236237870, 0, 13 | -1.07406322530303, 0, -0.35136418446315, 0, -0.14207821409532, 0, 14 | -0.03984144487100]) 15 | 16 | X0 = np.linspace(0, 10, 11) 17 | X1 = np.linspace(0, 10, 10) 18 | 19 | def test_simple_direct(self): 20 | """Test 1d input, direct implementation.""" 21 | assert_array_almost_equal(self.Y0, direct_dctii(self.X0)) 22 | assert_array_almost_equal(self.Y1, direct_dctii(self.X1)) 23 | 24 | def test_simple_direct2(self): 25 | """Test 1d input, 2nd direct implementation.""" 26 | assert_array_almost_equal(self.Y0, direct_dctii_2(self.X0)) 27 | assert_array_almost_equal(self.Y1, direct_dctii_2(self.X1)) 28 | 29 | def test_simple(self): 30 | """Test 1d input, fft implementation.""" 31 | assert_array_almost_equal(self.Y0, dctii(self.X0)) 32 | assert_array_almost_equal(self.Y1, dctii(self.X1)) 33 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | #[egg_info] 2 | #tag_build = dev 3 | #tag_svn_revision = 1 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # Last Change: Thu Mar 26 06:00 PM 2009 J 3 | 4 | # Copyright (C) 2008 Cournapeau David 5 | 6 | import os 7 | import sys 8 | 9 | import setuptools 10 | from numpy.distutils.core import setup 11 | 12 | from common import * 13 | 14 | def configuration(parent_package='', top_path=None, package_name=DISTNAME): 15 | if os.path.exists('MANIFEST'): os.remove('MANIFEST') 16 | 17 | from numpy.distutils.misc_util import Configuration 18 | config = Configuration(None, parent_package, top_path, 19 | namespace_packages = ['scikits']) 20 | 21 | config.set_options( 22 | ignore_setup_xxx_py=True, 23 | assume_default_configuration=True, 24 | delegate_options_to_subpackages=True, 25 | quiet=True, 26 | ) 27 | 28 | config.add_subpackage('scikits') 29 | config.add_subpackage(DISTNAME) 30 | config.add_data_files('scikits/__init__.py') 31 | 32 | return config 33 | 34 | if __name__ == "__main__": 35 | setup(configuration = configuration, 36 | install_requires = 'numpy', 37 | packages = setuptools.find_packages(), 38 | include_package_data = True, 39 | name = DISTNAME, 40 | version = VERSION, 41 | description = DESCRIPTION, 42 | long_description = LONG_DESCRIPTION, 43 | license = LICENSE, 44 | #test_suite="tester", # for python setup.py test 45 | zip_safe = False, # because of tests 46 | classifiers = CLASSIFIERS, 47 | ) 48 | --------------------------------------------------------------------------------