├── sigpyproc ├── __init__.py ├── ctype_helper.py ├── Utils.py ├── HeaderParams.py ├── TimeSeries.py ├── FoldedData.py ├── FourierSeries.py ├── Readers.py ├── Header.py └── Filterbank.py ├── c_src ├── .#SigPyProcSpec.c ├── Makefile ├── libSigPyProc.c ├── libSigPyProc8.c ├── libSigPyProc32.c ├── #SigPyProcSpec.c# ├── libSigPyProcSpec.c ├── libSigPyProcTim.c └── MersenneTwister.c ├── sigpyproc.pdf ├── examples ├── tutorial.fil ├── dedisperse.py └── get_bandpass.py ├── .gitignore ├── .travis.yml ├── README.md ├── Dockerfile └── setup.py /sigpyproc/__init__.py: -------------------------------------------------------------------------------- 1 | from .Readers import FilReader -------------------------------------------------------------------------------- /c_src/.#SigPyProcSpec.c: -------------------------------------------------------------------------------- 1 | ebarr@pc20212.mpifr-bonn.mpg.de.27869:1359977210 -------------------------------------------------------------------------------- /sigpyproc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ewanbarr/sigpyproc/HEAD/sigpyproc.pdf -------------------------------------------------------------------------------- /examples/tutorial.fil: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ewanbarr/sigpyproc/HEAD/examples/tutorial.fil -------------------------------------------------------------------------------- /examples/dedisperse.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from sigpyproc.Readers import FilReader 3 | fil = FilReader(argv[1]) 4 | tim = fil.dedisperse(float(argv[2])).toFile("%s_DM%s.tim"%(fil.header.basename,argv[2])) 5 | -------------------------------------------------------------------------------- /sigpyproc/ctype_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ctypes as C 3 | 4 | THIS_DIRPATH = os.path.dirname(os.path.abspath(__file__)) 5 | PARENT_DIRPATH = os.path.abspath(os.path.join(THIS_DIRPATH, '..')) 6 | 7 | def load_lib(libname): 8 | """ Load a shared library .so file in same directory as this module 9 | 10 | args: 11 | libname (str): name of library.so to load. 12 | 13 | Returns a ctypes.CDLL object 14 | """ 15 | lib = C.CDLL(os.path.join(PARENT_DIRPATH, libname)) 16 | return lib -------------------------------------------------------------------------------- /examples/get_bandpass.py: -------------------------------------------------------------------------------- 1 | from sigpyproc.Readers import FilReader 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | 5 | #compute the bandpass and frequency of each channel 6 | my_bpass = FilReader("tutorial.fil").bandpass() 7 | freqs = np.linspace(my_bpass.header.ftop, my_bpass.header.fbottom, my_bpass.size) 8 | 9 | #plot the result 10 | plt.plot(freqs,my_bpass) 11 | plt.title("Bandpass of tutorial.fil") 12 | plt.xlabel("Observing frequency (MHz)") 13 | plt.ylabel("Power (Arbitrary units)") 14 | plt.show() 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *~ 3 | 4 | # C extensions 5 | *.so 6 | *.o 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | #sigpyproc specific 40 | working_dir/ 41 | Docs/ 42 | build/ 43 | lib/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | 5 | # command to install dependencies 6 | install: 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq build-essential python-setuptools python-pip python-tk 9 | - sudo apt-get install -qq git curl wget make cmake fftw3 fftw3-dev pkg-config libiomp-dev libopenmpi-dev 10 | - pip install --upgrade pip 11 | - pip install --upgrade setuptools 12 | - pip install numpy matplotlib ipython 13 | - pip install . 14 | 15 | # Run tests 16 | script: 17 | - python examples/dedisperse.py examples/tutorial.fil 300 18 | 19 | branches: 20 | only: 21 | - master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sigpyproc 2 | ========= 3 | 4 | Installation 5 | ------------ 6 | 7 | ### Requirements 8 | 9 | 10 | * numpy 11 | * ctypes 12 | * FFTW3 13 | * OpenMP 14 | 15 | ### Step-by-step guide 16 | 17 | Once you have all the requirements installed, you can install this via pip: 18 | 19 | ``` 20 | pip install git+https://github.com/telegraphic/sigpyproc 21 | ``` 22 | 23 | Or, download / clone this repository, and then run 24 | 25 | ``` 26 | python setup.py install 27 | ``` 28 | 29 | ### Docker 30 | 31 | This repo now comes with a `Dockerfile`, so you can build a simple docker container with `sigpyproc` in it. To do so, clone this directory, cd into it, and then run on your command line: 32 | 33 | ``` 34 | docker build --tag sigpyproc . 35 | ``` 36 | 37 | You can then run the container with 38 | 39 | ``` 40 | docker run --rm -it sigpyproc 41 | ``` 42 | 43 | (Have a read of docker tutorials and documentation for more details!) 44 | 45 | 46 | [![Build Status](https://travis-ci.org/telegraphic/sigpyproc.svg?branch=master)](https://travis-ci.org/telegraphic/sigpyproc) 47 | 48 | 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # To compile this with docker use: 2 | # docker build --tag sigpyproc . 3 | # Then to run it: 4 | # docker run --rm -it sigpyproc 5 | # To be able to access local disk on Mac OSX, you need to use Docker for Mac GUI 6 | # and click on 'File sharing', then add your directory, e.g. /data/bl_pks 7 | # Then to run it: 8 | # docker run --rm -it -v /data/bl_pks:/mnt/data sigpyproc 9 | # And if you want to access a port, you need to do a similar thing: 10 | # docker run --rm -it -p 9876:9876 sigpyproc 11 | 12 | # INSTALL BASE FROM KERN SUITE 13 | FROM kernsuite/base:3 14 | ARG DEBIAN_FRONTEND=noninteractive 15 | 16 | ENV TERM xterm 17 | 18 | ###### 19 | # Do docker apt-installs 20 | RUN docker-apt-install build-essential python-setuptools python-pip python-tk 21 | RUN docker-apt-install git 22 | RUN docker-apt-install curl wget 23 | RUN docker-apt-install make cmake 24 | RUN docker-apt-install fftw3 fftw3-dev pkg-config 25 | RUN docker-apt-install libomp-dev 26 | 27 | ##### 28 | # Pip installation of python packages 29 | # Q. Would using CONDA be better? 30 | RUN pip install --upgrade pip 31 | RUN pip install --upgrade setuptools 32 | RUN pip install numpy matplotlib ipython 33 | 34 | # Finally, install sigpyproc! 35 | COPY . . 36 | RUN python setup.py install 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /c_src/Makefile: -------------------------------------------------------------------------------- 1 | FFTWFLAGS = -L${FFTW_PATH}/lib -I${FFTW_PATH}/include -lfftw3f 2 | 3 | libSigPyProc32.so: libSigPyProc32.o 4 | gcc -Wl,--no-as-needed -shared -lgomp -o libSigPyProc32.so libSigPyProc32.o 5 | 6 | libSigPyProc32.o: libSigPyProc32.c Makefile 7 | gcc -O3 -lm -fopenmp -DHAVE_OPENMP -fPIC -c $< -o $@ 8 | 9 | libSigPyProc8.so: libSigPyProc8.o MersenneTwister.o 10 | gcc -Wl,--no-as-needed -shared -lgomp -o libSigPyProc8.so libSigPyProc8.o MersenneTwister.o 11 | 12 | libSigPyProc8.o: libSigPyProc8.c Makefile 13 | gcc -O3 -lm -fopenmp -DHAVE_OPENMP -fPIC -c $< -o $@ 14 | 15 | libSigPyProc.so: libSigPyProc.o 16 | gcc -Wl,--no-as-needed -shared -o libSigPyProc.so libSigPyProc.o 17 | 18 | libSigPyProc.o: libSigPyProc.c Makefile 19 | gcc -O3 -lm -fPIC -c $< -o $@ 20 | 21 | libSigPyProcTim.so: libSigPyProcTim.o 22 | gcc -Wl,--no-as-needed -shared $(FFTWFLAGS) -lgomp -o libSigPyProcTim.so libSigPyProcTim.o 23 | 24 | libSigPyProcTim.o: libSigPyProcTim.c Makefile 25 | gcc -O3 $(FFTWFLAGS) -lm -fopenmp -DHAVE_OPENMP -fPIC -c $< -o $@ 26 | 27 | libSigPyProcSpec.so: libSigPyProcSpec.o 28 | gcc -Wl,--no-as-needed -shared $(FFTWFLAGS) -lgomp -o libSigPyProcSpec.so libSigPyProcSpec.o 29 | 30 | libSigPyProcSpec.o: libSigPyProcSpec.c Makefile 31 | gcc -O3 $(FFTWFLAGS) -lm -fopenmp -DHAVE_OPENMP -fPIC -c $< -o $@ 32 | 33 | MersenneTwister.o: MersenneTwister.c Makefile 34 | gcc -O3 -lm -fPIC -c $< -o $@ 35 | 36 | all: 37 | make libSigPyProc32.so 38 | make libSigPyProc8.so 39 | make libSigPyProc.so 40 | make libSigPyProcTim.so 41 | make libSigPyProcSpec.so 42 | 43 | install: 44 | mv *.so ../lib/ 45 | rm -rf *.o 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -f *.o *.so* 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension #find_packages 2 | 3 | __version__ = '0.1.1' 4 | 5 | def describe(filename): 6 | f = open(filename, "r") 7 | lines = f.readlines() 8 | return "".join(lines) 9 | 10 | 11 | ext0 = Extension('MersenneTwister', 12 | sources=['./c_src/MersenneTwister.c'], 13 | extra_link_args=["-lm"], 14 | extra_compile_args=["-Wno-strict-prototypes"] 15 | ) 16 | 17 | ext1 = Extension('libSigPyProc8', 18 | sources=[ 19 | './c_src/libSigPyProc8.c', 20 | './c_src/MersenneTwister.c', 21 | ], 22 | extra_link_args=["-lgomp", "-lm"], 23 | extra_compile_args=["-fopenmp", "-Wno-unused-variable", "-Wno-strict-prototypes"], 24 | ) 25 | 26 | ext2 = Extension('libSigPyProc32', 27 | sources=[ 28 | './c_src/libSigPyProc32.c', 29 | ], 30 | extra_link_args=["-lgomp", "-lm"], 31 | extra_compile_args=["-fopenmp", "-Wno-unused-variable", "-Wno-strict-prototypes"], 32 | ) 33 | 34 | ext3 = Extension('libSigPyProcSpec', 35 | sources=[ 36 | './c_src/libSigPyProcSpec.c', 37 | ], 38 | extra_link_args=["-lgomp", "-lm", "-lfftw3", "-lfftw3f"], 39 | extra_compile_args=["-fopenmp", "-Wno-unused-variable", "-Wno-strict-prototypes"], 40 | ) 41 | 42 | ext4 = Extension('libSigPyProcTim', 43 | sources=[ 44 | './c_src/libSigPyProcTim.c', 45 | ], 46 | extra_link_args=["-lgomp", "-lm", "-lfftw3", "-lfftw3f"], 47 | extra_compile_args=["-fopenmp", "-Wno-unused-variable", "-Wno-strict-prototypes"], 48 | ) 49 | 50 | ext5 = Extension('libSigPyProc', 51 | sources=[ 52 | './c_src/libSigPyProc.c', 53 | ], 54 | extra_link_args=["-lgomp"], 55 | extra_compile_args=["-fopenmp", "-Wno-unused-variable", "-Wno-strict-prototypes"], 56 | ) 57 | 58 | setup(name='sigpyproc', 59 | version=__version__, 60 | description='Python pulsar data toolbox', 61 | install_requires = ['numpy'], 62 | author='Ewan Barr', 63 | author_email='ewan.d.barr@googlemail.com', 64 | long_description=describe('README.md'), 65 | ext_modules=[ext0, ext1, ext2, ext3, ext4, ext5], 66 | packages=['sigpyproc'], 67 | zip_safe=False 68 | ) 69 | -------------------------------------------------------------------------------- /c_src/libSigPyProc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #define HI4BITS 240 6 | #define LO4BITS 15 7 | #define HI2BITS 192 8 | #define UPMED2BITS 48 9 | #define LOMED2BITS 12 10 | #define LO2BITS 3 11 | 12 | /*----------------------------------------------------------------------------*/ 13 | 14 | /*Function to unpack 1,2 and 4 bit data 15 | data is unpacked into an empty buffer 16 | Note: Only unpacks big endian bit ordering*/ 17 | void unpack(unsigned char* inbuffer, 18 | unsigned char* outbuffer, 19 | int nbits, 20 | int nbytes) 21 | { 22 | int ii,jj; 23 | switch(nbits){ 24 | case 1: 25 | for(ii=0;ii>jj)&1; 28 | } 29 | } 30 | break; 31 | case 2: 32 | for(ii=0;ii> 2; 35 | outbuffer[(ii*4)+1] = (inbuffer[ii] & UPMED2BITS) >> 4; 36 | outbuffer[(ii*4)+0] = (inbuffer[ii] & HI2BITS) >> 6; 37 | } 38 | break; 39 | case 4: 40 | for(ii=0;ii> 4; 43 | } 44 | break; 45 | } 46 | } 47 | 48 | /*Function to unpack 1,2 and 4 bit data 49 | data is unpacked into the same buffer 50 | this is done by unpacking the bytes backwards 51 | so as not to overwrite any of the data. 52 | This is old code that is no longer used should the 53 | filterbank reader ever be changed from using np.fromfile 54 | this may once again become useful 55 | Note: Only set up for big endian bit ordering*/ 56 | void unpackInPlace(unsigned char* buffer, 57 | int nbits, 58 | int nbytes) 59 | { 60 | int ii,jj,pos; 61 | int lastsamp = nbits*nbytes/8; 62 | unsigned char temp; 63 | 64 | switch(nbits){ 65 | case 1: 66 | for(ii=lastsamp-1;ii>-1;ii--){ 67 | temp = buffer[ii]; 68 | pos = ii*8; 69 | for (jj=0;jj<8;jj++) 70 | buffer[pos+jj] = (temp>>jj)&1; 71 | } 72 | break; 73 | case 2: 74 | for(ii=lastsamp-1;ii>-1;ii--){ 75 | temp = buffer[ii]; 76 | pos = ii*4; 77 | buffer[pos+3] = temp & LO2BITS; 78 | buffer[pos+2] = (temp & LOMED2BITS) >> 2; 79 | buffer[pos+1] = (temp & UPMED2BITS) >> 4; 80 | buffer[pos+0] = (temp & HI2BITS) >> 6; 81 | } 82 | break; 83 | case 4: 84 | for(ii=lastsamp-1;ii>-1;ii--){ 85 | temp = buffer[ii]; 86 | pos = ii*2; 87 | buffer[pos+0] = temp & LO4BITS; 88 | buffer[pos+1] = (temp & HI4BITS) >> 4; 89 | } 90 | break; 91 | } 92 | } 93 | 94 | /*Function to pack bit data into an empty buffer*/ 95 | void pack(unsigned char* buffer, 96 | unsigned char* outbuffer, 97 | int nbits, 98 | int nbytes) 99 | { 100 | int ii,jj,pos; 101 | int times = pow(nbits,2); 102 | int bitfact = 8/nbits; 103 | unsigned char val; 104 | 105 | switch(nbits){ 106 | case 1: 107 | for(ii=0;ii 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /*----------------------------------------------------------------------------*/ 9 | 10 | unsigned char getRand(float mean, float std){ 11 | unsigned char randval; 12 | randval = (std*normDist() + mean); 13 | if(randval>255) 14 | randval = 255; 15 | else if(randval<0) 16 | randval = 0; 17 | return randval; 18 | } 19 | 20 | void getTim(unsigned char* inbuffer,float* outbuffer,int nchans,int nsamps,int index){ 21 | int ii,jj,val; 22 | #pragma omp parallel for default(shared) private(jj,ii) 23 | for (ii=0;ii maxbuffer[jj]) 214 | maxbuffer[jj]=val; 215 | else if( val < minbuffer[jj] ) 216 | minbuffer[jj]=val; 217 | } 218 | } 219 | 220 | } 221 | 222 | void invertFreq(unsigned char* inbuffer,unsigned char* outbuffer,int nchans,int nsamps) 223 | { 224 | 225 | int ii,jj; 226 | #pragma omp parallel for default(shared) private(jj,ii) shared(outbuffer,inbuffer) 227 | for (ii = 0; ii < nsamps; ii++){ 228 | for (jj = 0; jj < nchans; jj++){ 229 | outbuffer[(jj)+ii*nchans] = inbuffer[(nchans-1-jj)+ii*nchans]; 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /sigpyproc/Utils.py: -------------------------------------------------------------------------------- 1 | import ctypes as C 2 | import numpy as np 3 | import warnings 4 | from numpy.ctypeslib import as_ctypes as as_c 5 | from sigpyproc.HeaderParams import nbits_to_dtype 6 | 7 | from .ctype_helper import load_lib 8 | lib = load_lib("libSigPyProc.so") 9 | 10 | class File(file): 11 | """A class to handle writing of arbitrary bit size data to file. 12 | 13 | :param filename: name of file to open 14 | :type filename: :func:`str` 15 | 16 | :param mode: file access mode, can be either "r", "r+", "w" or "a". 17 | :type mode: :func:`str` 18 | 19 | :param nbits: the bit size of units to be read from or written to file 20 | :type nbits: 21 | 22 | .. note:: 23 | 24 | The File class handles all packing and unpacking of sub-byte size data 25 | under the hood, so all calls can be made requesting numbers of units 26 | rather than numbers of bits or bytes. 27 | """ 28 | 29 | def __init__(self,filename,mode,nbits=8): 30 | file.__init__(self,filename,mode) 31 | self.nbits = nbits 32 | self.dtype = nbits_to_dtype[self.nbits] 33 | if nbits in [1,2,4]: 34 | self.bitfact = nbits/8. 35 | self.unpack = True 36 | else: 37 | self.bitfact = 1 38 | self.unpack = False 39 | 40 | def cread(self,nunits): 41 | """Read nunits of data from the file. 42 | 43 | :param nunits: number of units to be read from file 44 | :type nunits: int 45 | 46 | :return: an array containing the read data 47 | :rtype: :class:`numpy.ndarray` 48 | """ 49 | 50 | count = int(nunits*self.bitfact) 51 | data = np.fromfile(self,count=count,dtype=self.dtype) 52 | if self.unpack: 53 | unpacked = np.empty(nunits,dtype=self.dtype) 54 | lib.unpack(as_c(data), 55 | as_c(unpacked), 56 | C.c_int(self.nbits), 57 | C.c_int(data.size)) 58 | return unpacked 59 | else: 60 | return data 61 | 62 | def cwrite(self,ar): 63 | """Write an array to file. 64 | 65 | :param ar: a numpy array 66 | :type ar: :class:`numpy.ndarray` 67 | 68 | .. note:: 69 | 70 | Regardless of the dtype of the array argument, the data will be packed 71 | with a bitsize determined by the nbits attribute of the File instance. 72 | To change this attribute, use the _setNbits methods. 73 | It is the responsibility of the user to ensure that values in the array 74 | do not go beyond the maximum and minimum values allowed by the nbits 75 | attribute. 76 | """ 77 | if self.dtype != ar.dtype: 78 | warnings.warn("Given data (dtype={0}) will be unsafely cast to the \ 79 | requested dtype={1} before being written out to file"\ 80 | .format(ar.dtype, self.dtype), stacklevel=2) 81 | ar = ar.astype(self.dtype, casting='unsafe') 82 | 83 | #The lib.pack function has an assumption that the given array has 8-bit 84 | #data. If the given array was, say 32-bit floats and the requested nbits 85 | #is, say 2-bit, then the output will be garbage, hence the casting above is 86 | #necessary. 87 | if self.unpack: 88 | packed = np.empty(int(ar.size*self.bitfact),dtype=self.dtype) 89 | lib.pack(as_c(ar), 90 | as_c(packed), 91 | C.c_int(self.nbits), 92 | C.c_int(ar.size)) 93 | packed.tofile(self) 94 | else: 95 | ar.tofile(self) 96 | 97 | def __del__(self): 98 | self.close() 99 | 100 | def rollArray(y,shift,axis): 101 | """Roll the elements in the array by 'shift' positions along the 102 | given axis. 103 | 104 | Args: 105 | y -- array to roll 106 | shift -- number of bins to shift by 107 | axis -- axis to roll along 108 | 109 | Returns: shifted Ndarray 110 | """ 111 | y = np.asanyarray(y) 112 | n = y.shape[axis] 113 | shift %= n 114 | return y.take(np.concatenate((np.arange(shift,n),np.arange(shift))), axis) 115 | 116 | def _flattenList(n): 117 | new = [] 118 | repack = lambda x:[new.append(int(y)) for y in x] 119 | for elem in n: 120 | if hasattr(elem,"__iter__"): repack(elem) 121 | else: new.append(int(elem)) 122 | return new 123 | 124 | def stackRecarrays(arrays): 125 | """Wrapper for stacking :class:`numpy.recarrays`""" 126 | return arrays[0].__array_wrap__(np.hstack(arrays)) 127 | 128 | def nearestFactor(n,val): 129 | """Find nearest factor. 130 | 131 | :param n: number that we wish to factor 132 | :type n: int 133 | 134 | :param val: number that we wish to find nearest factor to 135 | :type val: int 136 | 137 | :return: nearest factor 138 | :rtype: int 139 | """ 140 | fact=[1,n] 141 | check=2 142 | rootn=np.sqrt(n) 143 | while check 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /*----------------------------------------------------------------------------*/ 9 | 10 | void getTim(float* inbuffer,float* outbuffer,int nchans,int nsamps,int index){ 11 | int ii,jj,val; 12 | #pragma omp parallel for default(shared) private(jj,ii) shared(outbuffer,inbuffer) 13 | for (ii=0;ii maxbuffer[jj]) 200 | maxbuffer[jj]=val; 201 | else if( val < minbuffer[jj] ) 202 | minbuffer[jj]=val; 203 | } 204 | } 205 | 206 | } 207 | 208 | void to8bit(float* inbuffer, 209 | unsigned char* outbuffer, 210 | unsigned char* flagbuffer, 211 | float* factbuffer, 212 | float* plusbuffer, 213 | float* flagMax, 214 | float* flagMin, 215 | int nsamps, 216 | int nchans) 217 | { 218 | int ii,jj; 219 | #pragma omp parallel for default(shared) private(jj,ii) 220 | for (ii=0;ii flagMax[jj]) 224 | flagbuffer[(ii*nchans)+jj] = 2; 225 | else if (inbuffer[(ii*nchans)+jj] < flagMin[jj]) 226 | flagbuffer[(ii*nchans)+jj] = 0; 227 | else 228 | flagbuffer[(ii*nchans)+jj] = 1; 229 | } 230 | } 231 | } 232 | 233 | void invertFreq(float* inbuffer,float* outbuffer,int nchans,int nsamps) 234 | { 235 | 236 | int ii,jj; 237 | #pragma omp parallel for default(shared) private(jj,ii) shared(outbuffer,inbuffer) 238 | for (ii = 0; ii < nsamps; ii++){ 239 | for (jj = 0; jj < nchans; jj++){ 240 | outbuffer[(jj)+ii*nchans] = inbuffer[(nchans-1-jj)+ii*nchans]; 241 | } 242 | } 243 | 244 | } 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /c_src/#SigPyProcSpec.c#: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*----------------------------------------------------------------------------*/ 11 | 12 | #define ELEM_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; } 13 | float median(float arr[], int n) 14 | { 15 | int low, high; 16 | int median; 17 | int middle, ll, hh; 18 | 19 | low = 0; 20 | high = n - 1; 21 | median = (low + high) / 2; 22 | for (;;) { 23 | if (high <= low) /* One element only */ 24 | return arr[median]; 25 | 26 | if (high == low + 1) { /* Two elements only */ 27 | if (arr[low] > arr[high]) 28 | ELEM_SWAP(arr[low], arr[high]); 29 | return arr[median]; 30 | } 31 | 32 | /* Find median of low, middle and high items; swap into position low */ 33 | middle = (low + high) / 2; 34 | if (arr[middle] > arr[high]) 35 | ELEM_SWAP(arr[middle], arr[high]); 36 | if (arr[low] > arr[high]) 37 | ELEM_SWAP(arr[low], arr[high]); 38 | if (arr[middle] > arr[low]) 39 | ELEM_SWAP(arr[middle], arr[low]); 40 | 41 | /* Swap low item (now in position middle) into position (low+1) */ 42 | ELEM_SWAP(arr[middle], arr[low + 1]); 43 | 44 | /* Nibble from each end towards middle, swapping items when stuck */ 45 | ll = low + 1; 46 | hh = high; 47 | for (;;) { 48 | do 49 | ll++; 50 | while (arr[low] > arr[ll]); 51 | do 52 | hh--; 53 | while (arr[hh] > arr[low]); 54 | 55 | if (hh < ll) 56 | break; 57 | 58 | ELEM_SWAP(arr[ll], arr[hh]); 59 | } 60 | 61 | /* Swap middle item (in position low) back into correct position */ 62 | ELEM_SWAP(arr[low], arr[hh]); 63 | 64 | /* Re-set active partition */ 65 | if (hh <= median) 66 | low = ll; 67 | if (hh >= median) 68 | high = hh - 1; 69 | } 70 | } 71 | #undef ELEM_SWAP 72 | 73 | 74 | void ccfft(float* buffer, 75 | float* result, 76 | int size) 77 | { 78 | fftwf_plan plan; 79 | fftw_complex* tempinput; 80 | 81 | plan = fftwf_plan_dft_1d(size, (fftwf_complex*) buffer, 82 | (fftwf_complex*) result, FFTW_BACKWARD, 83 | FFTW_ESTIMATE); 84 | fftwf_execute(plan); 85 | fftwf_destroy_plan(plan); 86 | } 87 | 88 | void ifft(float* buffer, 89 | float* result, 90 | int size) 91 | { 92 | fftwf_plan plan; 93 | plan = fftwf_plan_dft_c2r_1d(size, (fftwf_complex*) buffer, 94 | result,FFTW_ESTIMATE|FFTW_PRESERVE_INPUT); 95 | fftwf_execute(plan); 96 | fftwf_destroy_plan(plan); 97 | } 98 | 99 | void formSpecInterpolated(float* fftbuffer, 100 | float* specbuffer, 101 | int nsamps) 102 | { 103 | int ii; 104 | float i,r,a,b; 105 | float rl=0.0, il=0.0; 106 | for(ii=0;iinsamps-rindex/2) 176 | numread_new = nsamps-rindex/2; 177 | else 178 | numread_new = bufflen; 179 | 180 | for(ii=0;ii<2*numread_new;ii++) 181 | newinbuf[ii] = fftbuffer[ii+rindex]; 182 | rindex += 2*numread_new; 183 | 184 | for (ii = 0; ii < numread_new; ii++) { 185 | realbuffer[ii] = 0; 186 | realbuffer[ii] = 187 | newinbuf[ii*2] * newinbuf[ii*2] + 188 | newinbuf[ii*2+1] * newinbuf[ii*2+1]; 189 | } 190 | 191 | mean_new = median(realbuffer, numread_new) / log(2.0); 192 | slope = (mean_new - mean_old) / (numread_old + numread_new); 193 | 194 | for (ii = 0; ii < numread_old; ii++) { 195 | outbuffer[ii*2+windex] = 0.0; 196 | outbuffer[ii*2+1+windex] = 0.0; 197 | outbuffer[ii*2+windex]=oldinbuf[ii*2]/sqrt(mean_old+slope*((numread_old+numread_new) / 2.0 - ii)); 198 | outbuffer[ii*2+1+windex]=oldinbuf[ii*2+1]/sqrt(mean_old+slope*((numread_old+numread_new) / 2.0 - ii)); 199 | } 200 | windex+=2*numread_old; 201 | 202 | binnum += numread_new; 203 | if ((binnum * 1.0) / T < endfreq) 204 | bufflen = startwidth * log(binnum); 205 | else 206 | bufflen = endwidth; 207 | numread_old = numread_new; 208 | mean_old = mean_new; 209 | 210 | for (ii = 0; ii < 2*numread_new; ii++) { 211 | oldinbuf[ii] = 0; 212 | oldinbuf[ii] = newinbuf[ii]; 213 | } 214 | } 215 | for (ii = 0; ii < 2*numread_old; ii++) { 216 | outbuffer[ii+windex] = oldinbuf[ii] / sqrt(mean_old); 217 | } 218 | } 219 | 220 | void conjugate(float* specbuffer, 221 | float* outbuffer, 222 | int size) 223 | { 224 | int ii; 225 | int out_size = 2*size-2; 226 | memcpy(outbuffer,specbuffer,size*sizeof(float)); 227 | for (ii=0;ii 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*----------------------------------------------------------------------------*/ 11 | 12 | #define ELEM_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; } 13 | float median(float arr[], int n) 14 | { 15 | int low, high; 16 | int median; 17 | int middle, ll, hh; 18 | 19 | low = 0; 20 | high = n - 1; 21 | median = (low + high) / 2; 22 | for (;;) { 23 | if (high <= low) /* One element only */ 24 | return arr[median]; 25 | 26 | if (high == low + 1) { /* Two elements only */ 27 | if (arr[low] > arr[high]) 28 | ELEM_SWAP(arr[low], arr[high]); 29 | return arr[median]; 30 | } 31 | 32 | /* Find median of low, middle and high items; swap into position low */ 33 | middle = (low + high) / 2; 34 | if (arr[middle] > arr[high]) 35 | ELEM_SWAP(arr[middle], arr[high]); 36 | if (arr[low] > arr[high]) 37 | ELEM_SWAP(arr[low], arr[high]); 38 | if (arr[middle] > arr[low]) 39 | ELEM_SWAP(arr[middle], arr[low]); 40 | 41 | /* Swap low item (now in position middle) into position (low+1) */ 42 | ELEM_SWAP(arr[middle], arr[low + 1]); 43 | 44 | /* Nibble from each end towards middle, swapping items when stuck */ 45 | ll = low + 1; 46 | hh = high; 47 | for (;;) { 48 | do 49 | ll++; 50 | while (arr[low] > arr[ll]); 51 | do 52 | hh--; 53 | while (arr[hh] > arr[low]); 54 | 55 | if (hh < ll) 56 | break; 57 | 58 | ELEM_SWAP(arr[ll], arr[hh]); 59 | } 60 | 61 | /* Swap middle item (in position low) back into correct position */ 62 | ELEM_SWAP(arr[low], arr[hh]); 63 | 64 | /* Re-set active partition */ 65 | if (hh <= median) 66 | low = ll; 67 | if (hh >= median) 68 | high = hh - 1; 69 | } 70 | } 71 | #undef ELEM_SWAP 72 | 73 | 74 | void ccfft(float* buffer, 75 | float* result, 76 | int size) 77 | { 78 | fftwf_plan plan; 79 | fftw_complex* tempinput; 80 | 81 | plan = fftwf_plan_dft_1d(size, (fftwf_complex*) buffer, 82 | (fftwf_complex*) result, FFTW_BACKWARD, 83 | FFTW_ESTIMATE); 84 | fftwf_execute(plan); 85 | fftwf_destroy_plan(plan); 86 | } 87 | 88 | void ifft(float* buffer, 89 | float* result, 90 | int size) 91 | { 92 | fftwf_plan plan; 93 | plan = fftwf_plan_dft_c2r_1d(size, (fftwf_complex*) buffer, 94 | result,FFTW_ESTIMATE|FFTW_PRESERVE_INPUT); 95 | fftwf_execute(plan); 96 | fftwf_destroy_plan(plan); 97 | } 98 | 99 | void formSpecInterpolated(float* fftbuffer, 100 | float* specbuffer, 101 | int nsamps) 102 | { 103 | int ii; 104 | float i,r,a,b; 105 | float rl=0.0, il=0.0; 106 | for(ii=0;iinsamps-rindex/2) 176 | numread_new = nsamps-rindex/2; 177 | else 178 | numread_new = bufflen; 179 | 180 | for(ii=0;ii<2*numread_new;ii++) 181 | newinbuf[ii] = fftbuffer[ii+rindex]; 182 | rindex += 2*numread_new; 183 | 184 | for (ii = 0; ii < numread_new; ii++) { 185 | realbuffer[ii] = 0; 186 | realbuffer[ii] = 187 | newinbuf[ii*2] * newinbuf[ii*2] + 188 | newinbuf[ii*2+1] * newinbuf[ii*2+1]; 189 | } 190 | 191 | mean_new = median(realbuffer, numread_new) / log(2.0); 192 | slope = (mean_new - mean_old) / (numread_old + numread_new); 193 | 194 | for (ii = 0; ii < numread_old; ii++) { 195 | outbuffer[ii*2+windex] = 0.0; 196 | outbuffer[ii*2+1+windex] = 0.0; 197 | outbuffer[ii*2+windex]=oldinbuf[ii*2]/ 198 | sqrt(mean_old+slope*((numread_old+numread_new) / 2.0 - ii)); 199 | outbuffer[ii*2+1+windex]=oldinbuf[ii*2+1]/ 200 | sqrt(mean_old+slope*((numread_old+numread_new) / 2.0 - ii)); 201 | } 202 | windex+=2*numread_old; 203 | 204 | binnum += numread_new; 205 | if ((binnum * 1.0) / T < endfreq) 206 | bufflen = startwidth * log(binnum); 207 | else 208 | bufflen = endwidth; 209 | numread_old = numread_new; 210 | mean_old = mean_new; 211 | 212 | for (ii = 0; ii < 2*numread_new; ii++) { 213 | oldinbuf[ii] = 0; 214 | oldinbuf[ii] = newinbuf[ii]; 215 | } 216 | } 217 | for (ii = 0; ii < 2*numread_old; ii++) { 218 | outbuffer[ii+windex] = oldinbuf[ii] / sqrt(mean_old); 219 | } 220 | } 221 | 222 | void conjugate(float* specbuffer, 223 | float* outbuffer, 224 | int size) 225 | { 226 | int ii; 227 | int out_size = 2*size-2; 228 | memcpy(outbuffer,specbuffer,size*sizeof(float)); 229 | for (ii=0;ii 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //RUNNING MEDIAN CODE START 10 | typedef float Item; 11 | typedef struct Mediator_t 12 | { 13 | Item* data; //circular queue of values 14 | int* pos; //index into `heap` for each value 15 | int* heap; //max/median/min heap holding indexes into `data`. 16 | int N; //allocated size. 17 | int idx; //position in circular queue 18 | int minCt; //count of items in min heap 19 | int maxCt; //count of items in max heap 20 | } Mediator; 21 | 22 | /*--- Helper Functions ---*/ 23 | 24 | //returns 1 if heap[i] < heap[j] 25 | inline int mmless(Mediator* m, int i, int j) 26 | { 27 | return (m->data[m->heap[i]] < m->data[m->heap[j]]); 28 | } 29 | 30 | //swaps items i&j in heap, maintains indexes 31 | int mmexchange(Mediator* m, int i, int j) 32 | { 33 | int t = m->heap[i]; 34 | m->heap[i]=m->heap[j]; 35 | m->heap[j]=t; 36 | m->pos[m->heap[i]]=i; 37 | m->pos[m->heap[j]]=j; 38 | return 1; 39 | } 40 | 41 | //swaps items i&j if iminCt; i*=2) 51 | { if (i < m->minCt && mmless(m, i+1, i)) { ++i; } 52 | if (!mmCmpExch(m,i,i/2)) { break; } 53 | } 54 | } 55 | 56 | //maintains maxheap property for all items below i. (negative indexes) 57 | void maxSortDown(Mediator* m, int i) 58 | { 59 | for (i*=2; i >= -m->maxCt; i*=2) 60 | { if (i > -m->maxCt && mmless(m, i, i-1)) { --i; } 61 | if (!mmCmpExch(m,i/2,i)) { break; } 62 | } 63 | } 64 | 65 | //maintains minheap property for all items above i, including median 66 | //returns true if median changed 67 | inline int minSortUp(Mediator* m, int i) 68 | { 69 | while (i>0 && mmCmpExch(m,i,i/2)) i/=2; 70 | return (i==0); 71 | } 72 | 73 | //maintains maxheap property for all items above i, including median 74 | //returns true if median changed 75 | inline int maxSortUp(Mediator* m, int i) 76 | { 77 | while (i<0 && mmCmpExch(m,i/2,i)) i/=2; 78 | return (i==0); 79 | } 80 | /*--- Public Interface ---*/ 81 | 82 | 83 | //creates new Mediator: to calculate `nItems` running median. 84 | //mallocs single block of memory, caller must free. 85 | Mediator* MediatorNew(int nItems) 86 | { 87 | int size = sizeof(Mediator)+nItems*(sizeof(Item)+sizeof(int)*2); 88 | Mediator* m= malloc(size); 89 | m->data= (Item*)(m+1); 90 | m->pos = (int*) (m->data+nItems); 91 | m->heap = m->pos+nItems + (nItems/2); //points to middle of storage. 92 | m->N=nItems; 93 | m->minCt = m->maxCt = m->idx = 0; 94 | while (nItems--) //set up initial heap fill pattern: median,max,min,max,... 95 | { m->pos[nItems]= ((nItems+1)/2) * ((nItems&1)?-1:1); 96 | m->heap[m->pos[nItems]]=nItems; 97 | } 98 | return m; 99 | } 100 | 101 | 102 | //Inserts item, maintains median in O(lg nItems) 103 | void MediatorInsert(Mediator* m, Item v) 104 | { 105 | int p = m->pos[m->idx]; 106 | Item old = m->data[m->idx]; 107 | m->data[m->idx]=v; 108 | m->idx = (m->idx+1) % m->N; 109 | if (p>0) //new item is in minHeap 110 | { if (m->minCt < (m->N-1)/2) { m->minCt++; } 111 | else if (v>old) { minSortDown(m,p); return; } 112 | if (minSortUp(m,p) && mmCmpExch(m,0,-1)) { maxSortDown(m,-1); } 113 | } 114 | else if (p<0) //new item is in maxheap 115 | { if (m->maxCt < m->N/2) { m->maxCt++; } 116 | else if (vminCt && mmCmpExch(m,1,0)) { minSortDown(m,1); } 118 | } 119 | else //new item is at median 120 | { if (m->maxCt && maxSortUp(m,-1)) { maxSortDown(m,-1); } 121 | if (m->minCt && minSortUp(m, 1)) { minSortDown(m, 1); } 122 | } 123 | } 124 | 125 | //returns median item (or average of 2 when item count is even) 126 | Item MediatorMedian(Mediator* m) 127 | { 128 | Item v= m->data[m->heap[0]]; 129 | if (m->minCtmaxCt) { v=(v+m->data[m->heap[-1]])/2; } 130 | return v; 131 | } 132 | 133 | void runningMedian(float* inbuffer, float* outbuffer, int window, int nsamps) 134 | { 135 | int ii; 136 | Mediator* m = MediatorNew(window); 137 | for(ii=0;ii 1) 260 | output[index-1] = input[ii]; 261 | last_bin = index; 262 | } 263 | } 264 | 265 | 266 | -------------------------------------------------------------------------------- /sigpyproc/TimeSeries.py: -------------------------------------------------------------------------------- 1 | from numpy.ctypeslib import as_ctypes as as_c 2 | import numpy as np 3 | import ctypes as C 4 | 5 | from .ctype_helper import load_lib 6 | lib = load_lib("libSigPyProcTim.so") 7 | 8 | class TimeSeries(np.ndarray): 9 | """Class for handling pulsar data in time series. 10 | 11 | :param input_array: 1 dimensional array of shape (nsamples) 12 | :type input_array: :class:`numpy.ndarray` 13 | 14 | :param header: observational metadata 15 | :type header: :class:`~sigpyproc.Header.Header` 16 | """ 17 | def __new__(cls,input_array,header): 18 | if getattr(input_array,"dtype",False) == np.dtype("float32"): 19 | obj = input_array.view(cls) 20 | else: 21 | obj = input_array.astype("float32").view(cls) 22 | obj.header = header 23 | return obj 24 | 25 | def __array_finalize__(self,obj): 26 | if obj is None: return 27 | if hasattr(obj,"header"): 28 | self.header = obj.header 29 | 30 | def fold(self,period,accel=0,nbins=50,nints=32): 31 | """Fold time series into discrete phase and subintegration bins. 32 | 33 | :param period: period in seconds to fold with 34 | :type period: float 35 | 36 | :param nbins: number of phase bins in output 37 | :type nbins: int 38 | 39 | :param nints: number of subintegrations in output 40 | :type nints: int 41 | 42 | :returns: data cube containing the folded data 43 | :rtype: :class:`~sigpyproc.FoldedData.FoldedData` 44 | """ 45 | if self.size/(nbins*nints) < 10: 46 | raise ValueError,"nbins x nints is too large for length of data" 47 | fold_ar = np.zeros(nbins*nints,dtype="float64") 48 | count_ar = np.zeros(nbins*nints,dtype="int32") 49 | lib.foldTim(as_c(self), 50 | as_c(fold_ar), 51 | as_c(count_ar), 52 | C.c_double(self.header.tsamp), 53 | C.c_double(period), 54 | C.c_double(accel), 55 | C.c_int(self.size), 56 | C.c_int(nbins), 57 | C.c_int(nints)) 58 | fold_ar/=count_ar 59 | fold_ar = fold_ar.reshape(nints,1,nbins) 60 | return FoldedData(fold_ar, 61 | self.header.newHeader(), 62 | period, 63 | self.header.refdm, 64 | accel) 65 | 66 | def rFFT(self): 67 | """Perform 1-D real to complex forward FFT. 68 | 69 | :return: output of FFTW3 70 | :rtype: :class:`~sigpyproc.FourierSeries.FourierSeries` 71 | """ 72 | if self.size%2 ==0: 73 | fftsize = self.size 74 | else: 75 | fftsize = self.size-1 76 | fft_ar = np.empty(fftsize+2,dtype="float32") 77 | lib.rfft(as_c(self), 78 | as_c(fft_ar), 79 | fftsize) 80 | return FourierSeries(fft_ar,self.header.newHeader()) 81 | 82 | def runningMean(self,window=10001): 83 | """Filter time series with a running mean. 84 | 85 | :param window: width in bins of running mean filter 86 | :type window: int 87 | 88 | :return: filtered time series 89 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 90 | 91 | .. note:: 92 | 93 | Window edges will be dealt with only at the start of the time series. 94 | 95 | """ 96 | tim_ar = np.empty_like(self) 97 | lib.runningMean(as_c(self), 98 | as_c(tim_ar), 99 | C.c_int(window), 100 | C.c_int(self.size)) 101 | return tim_ar.view(TimeSeries) 102 | 103 | def runningMedian(self,window=10001): 104 | """Filter time series with a running median. 105 | 106 | :param window: width in bins of running median filter 107 | :type window: int 108 | 109 | :returns: filtered time series 110 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 111 | 112 | .. note:: 113 | 114 | Window edges will be dealt with only at the start of the time series. 115 | """ 116 | tim_ar = np.empty_like(self) 117 | lib.runningMedian(as_c(self), 118 | as_c(tim_ar), 119 | C.c_int(window), 120 | C.c_int(self.size)) 121 | return tim_ar.view(TimeSeries) 122 | 123 | def applyBoxcar(self,width): 124 | """Apply a boxcar filter to the time series. 125 | 126 | :param width: width in bins of filter 127 | :type width: int 128 | 129 | :return: filtered time series 130 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 131 | 132 | .. note:: 133 | 134 | Time series returned is of size nsamples-width with width/2 removed removed from either end. 135 | """ 136 | tim_ar = np.empty_like(self) 137 | lib.runBoxcar(as_c(self), 138 | as_c(tim_ar), 139 | C.c_int(width), 140 | C.c_int(self.size)) 141 | return tim_ar.view(TimeSeries) 142 | 143 | def downsample(self,factor): 144 | """Downsample the time series. 145 | 146 | :param factor: factor by which time series will be downsampled 147 | :type factor: int 148 | 149 | :return: downsampled time series 150 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 151 | 152 | .. note:: 153 | 154 | Returned time series is of size nsamples//factor 155 | """ 156 | if factor == 1: return self 157 | newLen = self.size//factor 158 | tim_ar = np.zeros(newLen,dtype="float32") 159 | lib.downsampleTim(as_c(self), 160 | as_c(tim_ar), 161 | C.c_int(factor), 162 | C.c_int(newLen)) 163 | return TimeSeries(tim_ar,self.header.newHeader({'tsamp':self.header.tsamp*factor})) 164 | 165 | def toDat(self,basename): 166 | """Write time series in presto ``.dat`` format. 167 | 168 | :param basename: file basename for output ``.dat`` and ``.inf`` files 169 | :type basename: string 170 | 171 | :return: ``.dat`` file name and ``.inf`` file name 172 | :rtype: :func:`tuple` of :func:`str` 173 | 174 | .. note:: 175 | 176 | Method also writes a corresponding .inf file from the header data 177 | """ 178 | self.header.makeInf(outfile="%s.inf"%(basename)) 179 | datfile = open("%s.dat"%(basename),"w+") 180 | if self.size%2 != 0: 181 | self[:-1].tofile(datfile) 182 | else: 183 | self.tofile(datfile) 184 | return "%s.dat"%(basename),"%s.inf"%(basename) 185 | 186 | def toFile(self,filename): 187 | """Write time series in sigproc format. 188 | 189 | :param filename: output file name 190 | :type filename: str 191 | 192 | :return: output file name 193 | :rtype: :func:`str` 194 | """ 195 | outfile = self.header.prepOutfile(filename) 196 | self.tofile(outfile) 197 | return outfile.name 198 | 199 | def pad(self,npad): 200 | """Pad a time series with mean valued data. 201 | 202 | :param npad: number of padding points 203 | :type nzeros: int 204 | 205 | :return: padded time series 206 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 207 | """ 208 | new_ar = np.hstack((self,self.mean()*np.ones(npad))) 209 | return TimeSeries(new_ar,self.header.newHeader()) 210 | 211 | def resample(self,accel,jerk=0): 212 | """Perform time domain resampling to remove acceleration and jerk. 213 | 214 | :param accel: The acceleration to remove from the time series 215 | :type accel: float 216 | 217 | :param jerk: The jerk/jolt to remove from the time series 218 | :type jerk: float 219 | 220 | :param period: The mimimum period that the resampling 221 | will be sensitive to. 222 | :type period: float 223 | 224 | :return: resampled time series 225 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 226 | """ 227 | if accel > 0: 228 | new_size = self.size-1 229 | else: 230 | new_size = self.size 231 | out_ar = np.zeros(new_size,dtype="float32") 232 | print new_size 233 | lib.resample(as_c(self), 234 | as_c(out_ar), 235 | C.c_int(new_size), 236 | C.c_float(accel), 237 | C.c_float(self.header.tsamp)) 238 | 239 | new_header = self.header.newHeader({"nsamples":out_ar.size, 240 | "accel":accel}) 241 | return TimeSeries(out_ar,new_header) 242 | 243 | def correlate(self,other): 244 | """Cross correlate with another time series of the same length. 245 | 246 | :param other: array to correlate with 247 | :type other: :class:`numpy.ndarray` 248 | 249 | :return: time series containing the correlation 250 | :rtype: :class:`sigpyproc.TimeSeries.TimeSeries` 251 | """ 252 | if type(self) != type(other): 253 | try: 254 | other = TimeSeries(other,self.header.newHeader()) 255 | except: 256 | raise Exception("Could not convert argument to TimeSeries instance") 257 | return (self.rFFT()*other.rFFT()).iFFT() 258 | 259 | 260 | from sigpyproc.FoldedData import FoldedData 261 | from sigpyproc.FourierSeries import FourierSeries 262 | -------------------------------------------------------------------------------- /sigpyproc/FoldedData.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import ctypes as C 3 | from sigpyproc.Utils import rollArray 4 | from os import popen 5 | 6 | from .ctype_helper import load_lib 7 | lib = load_lib("libSigPyProc.so") 8 | 9 | class Profile(np.ndarray): 10 | """Class to handle a 1-D pulse profile. 11 | 12 | :param input_array: a pulse profile in array form 13 | :type input_array: :class:`numpy.ndarray` 14 | """ 15 | 16 | def __new__(cls,input_array): 17 | obj = input_array.astype("float32").view(cls) 18 | return obj 19 | 20 | def __array_finalize__(self,obj): 21 | if obj is None: return 22 | 23 | def _getWidth(self): 24 | self-=np.median(self) 25 | trial_widths = np.arange(1,self.size) 26 | convmaxs = np.array([np.convolve(np.ones(ii),self,mode="same").max() 27 | /np.sqrt(ii) for ii in trial_widths]) 28 | return trial_widths[convmaxs.argmax()] 29 | 30 | def _getPosition(self,width): 31 | return np.convolve(np.ones(width),self,mode="same").argmax() 32 | 33 | def _getBaseline(self,width): 34 | pos = self._getPosition(width) 35 | wing = np.ceil(width/2.) 36 | return np.hstack((self[:pos-wing],self[pos+wing+1:])) 37 | 38 | def SN(self): 39 | """Return a rudimentary signal-to-noise measure for the profile. 40 | 41 | .. note:: 42 | 43 | This is a bare-bones, quick-n'-dirty algorithm that should not be used for 44 | high quality signal-to-noise measurements. 45 | """ 46 | tmp_ar = self.copy() 47 | width= self._getWidth() 48 | baseline = self._getBaseline(width) 49 | tmp_ar-=baseline.mean() 50 | tmp_ar/=baseline.std() 51 | return float(tmp_ar.sum()/np.sqrt(width)) 52 | 53 | def retroProf(self,height=0.7,width=0.7): 54 | """Display the profile in ASCII formay in the terminal window. 55 | 56 | :param height: fraction of terminal rows to use 57 | :type height: float 58 | 59 | :param width: fraction of terminal columns to use 60 | :param width: 61 | 62 | .. note:: 63 | 64 | This function requires a system call to the Linux/Unix ``stty`` command. 65 | """ 66 | 67 | rows, columns = popen('stty size', 'r').read().split() 68 | rows = int(int(rows)*height) 69 | columns = int(int(columns)*width) 70 | bins = np.linspace(0,self.size-1,columns) 71 | new_prof = np.interp(bins,np.arange(self.size),self) 72 | new_prof -= new_prof.min() 73 | new_prof /= new_prof.max() 74 | new_prof *= rows 75 | for ii in np.arange(rows)[::-1]: 76 | print "".join([("#" if val >= ii else " ") for val in new_prof]) 77 | 78 | 79 | class FoldSlice(np.ndarray): 80 | """Class to handle a 2-D slice of a :class:`~sigpyproc.FoldedData.FoldedData` instance. 81 | 82 | :param input_array: a 2-D array with phase in x axis. 83 | :type input_array: :class:`numpy.ndarray` 84 | """ 85 | def __new__(cls,input_array): 86 | obj = input_array.astype("float32").view(cls) 87 | return obj 88 | 89 | def __array_finalize__(self,obj): 90 | if obj is None: return 91 | 92 | def normalise(self): 93 | """Normalise the slice by dividing each row by its mean. 94 | 95 | :return: normalised version of slice 96 | :rtype: :class:`~sigpyproc.FoldedData.FoldSlice` 97 | """ 98 | return self/self.mean(axis=1).reshape(self.shape[0],1) 99 | 100 | def getProfile(self): 101 | """Return the pulse profile from the slice. 102 | 103 | :return: a pulse profile 104 | :rtype: :class:`~sigpyproc.FoldedData.Profile` 105 | """ 106 | return self.sum(axis=0).view(Profile) 107 | 108 | class FoldedData(np.ndarray): 109 | """Class to handle a data cube produced by any of the sigpyproc folding methods. 110 | 111 | :param input_array: 3-D array of folded data 112 | :type input_array: :class:`numpy.ndarray` 113 | 114 | :param header: observational metadata 115 | :type header: :class:`~sigpyproc.Header.Header` 116 | 117 | :param period: period that data was folded with 118 | :type period: float 119 | 120 | :param dm: DM that data was folded with 121 | :type dm: float 122 | 123 | :param accel: accleration that data was folded with (def=0) 124 | :type accel: float 125 | 126 | .. note:: 127 | 128 | Data cube should have the shape: 129 | (number of subintegrations, number of subbands, number of profile bins) 130 | """ 131 | 132 | def __new__(cls,input_array,header,period,dm,accel=0): 133 | obj = input_array.astype("float32").view(cls) 134 | obj.header = header 135 | obj.period = period 136 | obj.dm = dm 137 | obj.accel = accel 138 | obj._setDefaults() 139 | return obj 140 | 141 | def __array_finalize__(self,obj): 142 | if obj is None: return 143 | self.header = getattr(obj,"header",None) 144 | self.period = getattr(obj,"period",None) 145 | self.dm = getattr(obj,"dm",None) 146 | self.accel = getattr(obj,"accel",None) 147 | 148 | def _setDefaults(self): 149 | self.nints = self.shape[0] 150 | self.nbands = self.shape[1] 151 | self.nbins = self.shape[2] 152 | self._orig = self.copy() 153 | self._period = self.period 154 | self._dm = self.dm 155 | self._tph_shifts = np.zeros(self.nints,dtype="int32") 156 | self._fph_shifts = np.zeros(self.nbands,dtype="int32") 157 | 158 | def getSubint(self,n): 159 | """Return a single subintegration from the data cube. 160 | 161 | :param n: subintegration number (n=0 is first subintegration) 162 | :type n: int 163 | 164 | :return: a 2-D array containing the subintegration 165 | :rtype: :class:`~sigpyproc.FoldedData.FoldSlice` 166 | """ 167 | return self[n].view(FoldSlice) 168 | 169 | def getSubband(self,n): 170 | """Return a single subband from the data cube. 171 | 172 | :param n: subband number (n=0 is first subband) 173 | :type n: int 174 | 175 | :return: a 2-D array containing the subband 176 | :rtype: :class:`~sigpyproc.FoldedData.FoldSlice` 177 | """ 178 | return self[:,n].view(FoldSlice) 179 | 180 | def getProfile(self): 181 | """Return a the data cube summed in time and frequency. 182 | 183 | :return: a 1-D array containing the power as a function of phase (pulse profile) 184 | :rtype: :class:`~sigpyproc.FoldedData.Profile` 185 | """ 186 | return self.sum(axis=0).sum(axis=0).view(Profile) 187 | 188 | def getTimePhase(self): 189 | """Return the data cube collapsed in frequency. 190 | 191 | :return: a 2-D array containing the time vs. phase plane 192 | :rtype: :class:`~sigpyproc.FoldedData.FoldSlice` 193 | """ 194 | return self.sum(axis=1).view(FoldSlice) 195 | 196 | def getFreqPhase(self): 197 | """Return the data cube collapsed in time. 198 | 199 | :return: a 2-D array containing the frequency vs. phase plane 200 | :rtype: :class:`~sigpyproc.FoldedData.FoldSlice` 201 | """ 202 | return self.sum(axis=0).view(FoldSlice) 203 | 204 | def centre(self): 205 | """Try and roll the data cube to center the pulse.""" 206 | p = self.getProfile() 207 | pos = p._getPosition(p._getWidth()) 208 | self = rollArray(self,(pos-self.nbins/2),2) 209 | 210 | def _replaceNan(self): 211 | bad_ids = np.where(np.isnan(self)) 212 | good_ids = np.where(np.isfinite(self)) 213 | med = np.median(self[good_ids]) 214 | self[bad_ids] =med 215 | 216 | def _normalise(self): 217 | self.freqPhase /= self.freqPhase.mean(axis=1).reshape(self.nbands,1) 218 | self.timePhase /= self.timePhase.mean(axis=1).reshape(self.nints,1) 219 | 220 | def _getDMdelays(self,dm): 221 | delta_dm = dm-self._dm 222 | if delta_dm == 0: 223 | drifts = -1*self._fph_shifts 224 | self._fph_shifts[:] = 0 225 | return drifts 226 | else: 227 | chan_width = self.header.foff*self.header.nchans/self.nbands 228 | freqs = (np.arange(self.nbands)*chan_width)+self.header.fch1 229 | fact = delta_dm * 4.148808e3 230 | drifts = (fact * ((freqs**-2)-(self.header.fch1**-2))/((self.period/self.nbins))) 231 | drifts = drifts.round().astype("int32") 232 | bin_drifts = drifts - self._fph_shifts 233 | self._fph_shifts = drifts 234 | return bin_drifts 235 | 236 | def _getPdelays(self,p): 237 | dbins = (p/self._period-1)*self.header.tobs*self.nbins/self._period 238 | if dbins == 0: 239 | drifts = -1*self._tph_shifts 240 | self._tph_shifts[:] = 0 241 | return drifts 242 | else: 243 | drifts = np.round(np.arange(float(self.nints))/(self.nints/dbins) 244 | ).astype("int32") 245 | bin_drifts = drifts-self._tph_shifts 246 | self._tph_shifts = drifts 247 | return bin_drifts 248 | 249 | def updateParams(self,dm=None,period=None): 250 | """Install a new folding period and/or DM in the data cube. 251 | 252 | :param dm: the new DM to dedisperse to 253 | :type dm: float 254 | 255 | :param period: the new period to fold with 256 | :type period: float 257 | """ 258 | 259 | if dm is None: 260 | dm = self.dm 261 | if period is None: 262 | period = self.period 263 | 264 | dmdelays = self._getDMdelays(dm) 265 | pdelays = self._getPdelays(period) 266 | for ii in range(self.nints): 267 | for jj in range(self.nbands): 268 | if dmdelays is not None: 269 | self[ii][jj] = rollArray(self[ii][jj],dmdelays[jj],0) 270 | if pdelays is not None: 271 | self[ii][jj] = rollArray(self[ii][jj],pdelays[ii],0) 272 | self.dm = dm 273 | self.period = period 274 | 275 | -------------------------------------------------------------------------------- /sigpyproc/FourierSeries.py: -------------------------------------------------------------------------------- 1 | import ctypes as C 2 | from sigpyproc.Utils import File 3 | from numpy.ctypeslib import as_ctypes as as_c 4 | import numpy as np 5 | 6 | from .ctype_helper import load_lib 7 | lib = load_lib("libSigPyProcSpec.so") 8 | 9 | 10 | class PowerSpectrum(np.ndarray): 11 | """Class to handle power spectra. 12 | 13 | :param input_array: 1 dimensional array of shape (nsamples) 14 | :type input_array: :class:`numpy.ndarray` 15 | 16 | :param header: observational metadata 17 | :type header: :class:`~sigpyproc.Header.Header` 18 | """ 19 | 20 | def __new__(cls,input_array,header): 21 | obj = input_array.astype("float32").view(cls) 22 | obj.header = header 23 | return obj 24 | 25 | def __array_finalize__(self,obj): 26 | if obj is None: return 27 | if hasattr(obj,"header"): 28 | self.header = obj.header 29 | 30 | def bin2freq(self,bin_): 31 | """Return centre frequency of a given bin. 32 | 33 | :param bin_: bin number 34 | :type bin_: int 35 | 36 | :return: frequency of bin 37 | :rtype: float 38 | """ 39 | return (bin_)/(self.header.tobs) 40 | 41 | def bin2period(self,bin_): 42 | """Return centre period of a given bin. 43 | 44 | :param bin_: bin number 45 | :type bin_: int 46 | 47 | :return: period of bin 48 | :rtype: float 49 | """ 50 | return 1/self.bin2freq(bin_) 51 | 52 | def freq2bin(self,freq): 53 | """Return nearest bin to a given frequency. 54 | 55 | :param freq: frequency 56 | :type freq: float 57 | 58 | :return: nearest bin to frequency 59 | :rtype: float 60 | """ 61 | return int(round(freq*self.header.tobs)) 62 | 63 | def period2bin(self,period): 64 | """Return nearest bin to a given periodicity. 65 | 66 | :param period: periodicity 67 | :type period: float 68 | 69 | :return: nearest bin to period 70 | :rtype: float 71 | """ 72 | return self.freq2bin(1/period) 73 | 74 | def harmonicFold(self,nfolds=1): 75 | """Perform Lyne-Ashworth harmonic folding of the power spectrum. 76 | 77 | :param nfolds: number of harmonic folds to perform (def=1) 78 | :type nfolds: int 79 | 80 | :return: A list of folded spectra where the i :sup:`th` element is the spectrum folded i times. 81 | :rtype: :func:`list` of :class:`~sigpyproc.FourierSeries.PowerSpectrum` 82 | """ 83 | 84 | sum_ar = self.copy() 85 | sum_ar_c = as_c(sum_ar) 86 | 87 | nfold1 = 0 #int(self.header.tsamp*2*self.size/maxperiod) 88 | folds = [] 89 | for ii in range(nfolds): 90 | nharm = 2**(ii+1) 91 | nfoldi =int(max(1,min(nharm*nfold1-nharm/2,self.size))) 92 | harm_ar = np.array([int(kk*ll/float(nharm)) 93 | for ll in range(nharm) 94 | for kk in range(1,nharm,2)]).astype("int32") 95 | 96 | facts_ar = np.array([(kk*nfoldi+nharm/2)/nharm for kk in range(1,nharm,2)]).astype("int32") 97 | 98 | lib.sumHarms(as_c(self), 99 | sum_ar_c, 100 | as_c(harm_ar), 101 | as_c(facts_ar), 102 | C.c_int(nharm), 103 | C.c_int(self.size), 104 | C.c_int(nfoldi)) 105 | 106 | new_header = self.header.newHeader({"tsamp":self.header.tsamp*nharm}) 107 | folds.append(PowerSpectrum(sum_ar,new_header)) 108 | return folds 109 | 110 | 111 | class FourierSeries(np.ndarray): 112 | """Class to handle output of FFT'd time series. 113 | 114 | :param input_array: 1 dimensional array of shape (nsamples) 115 | :type input_array: :class:`numpy.ndarray` 116 | 117 | :param header: observational metadata 118 | :type header: :class:`~sigpyproc.Header.Header` 119 | """ 120 | def __new__(cls,input_array,header): 121 | obj = input_array.astype("float32").view(cls) 122 | obj.header = header 123 | return obj 124 | 125 | def __array_finalize__(self,obj): 126 | if obj is None: return 127 | if hasattr(obj,"header"): 128 | self.header = obj.header 129 | 130 | def __mul__(self,other): 131 | if type(other) == type(self): 132 | if other.size != self.size: 133 | raise Exception("Instances must be the same size") 134 | else: 135 | out_ar = np.empty_like(self) 136 | lib.multiply_fs(as_c(self), 137 | as_c(other), 138 | as_c(out_ar), 139 | C.c_int(self.size)) 140 | return FourierSeries(out_ar,self.header.newHeader()) 141 | else: 142 | return super(FourierSeries,self).__mul__(other) 143 | 144 | def __rmul__(self,other): 145 | self.__mul__(other) 146 | 147 | def formSpec(self,interpolated=True): 148 | """Form power spectrum. 149 | 150 | :param interpolated: flag to set nearest bin interpolation (def=True) 151 | :type interpolated: bool 152 | 153 | :return: a power spectrum 154 | :rtype: :class:`~sigpyproc.FourierSeries.PowerSpectrum` 155 | """ 156 | spec_ar = np.empty(self.size/2,dtype="float32") 157 | if interpolated: 158 | lib.formSpecInterpolated(as_c(self), 159 | as_c(spec_ar), 160 | C.c_int(self.size/2)) 161 | else: 162 | lib.formSpec(as_c(self), 163 | as_c(spec_ar), 164 | C.c_int(self.size)) 165 | 166 | return PowerSpectrum(spec_ar,self.header.newHeader()) 167 | 168 | def iFFT(self): 169 | """Perform 1-D complex to real inverse FFT using FFTW3. 170 | 171 | :return: a time series 172 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 173 | """ 174 | tim_ar = np.empty(self.size-2,dtype="float32") 175 | lib.ifft(as_c(self), 176 | as_c(tim_ar), 177 | C.c_int(self.size-2)) 178 | return TimeSeries(tim_ar,self.header.newHeader()) 179 | 180 | def rednoise(self,startwidth=6,endwidth=100,endfreq=1.0): 181 | """Perform rednoise removal via Presto style method. 182 | 183 | :param startwidth: size of initial array for median calculation 184 | :type startwidth: int 185 | 186 | :param endwidth: size of largest array for median calculation 187 | :type endwidth: int 188 | 189 | :param endfreq: remove rednoise up to this frequency 190 | :type endfreq: float 191 | 192 | :return: whitened fourier series 193 | :rtype: :class:`~sigpyproc.FourierSeries.FourierSeries` 194 | 195 | """ 196 | out_ar = np.empty_like(self) 197 | buf_c1 = np.empty(2*endwidth,dtype="float32") 198 | buf_c2 = np.empty(2*endwidth,dtype="float32") 199 | buf_f1 = np.empty(endwidth,dtype="float32") 200 | lib.rednoise(as_c(self), 201 | as_c(out_ar), 202 | as_c(buf_c1), 203 | as_c(buf_c2), 204 | as_c(buf_f1), 205 | C.c_int(self.size/2), 206 | C.c_float(self.header.tsamp), 207 | C.c_int(startwidth), 208 | C.c_int(endwidth), 209 | C.c_float(endfreq)) 210 | return FourierSeries(out_ar,self.header.newHeader()) 211 | 212 | def conjugate(self): 213 | """Conjugate the Fourier series. 214 | 215 | :return: conjugated Fourier series. 216 | :rtype: :class:`sigpyproc.FourierSeries.FourierSeries` 217 | 218 | .. note:: 219 | 220 | Function assumes that the Fourier series is the non-conjugated 221 | product of a real to complex FFT. 222 | """ 223 | out_ar = np.empty(2*self.size-2,dtype="float32") 224 | lib.conjugate(as_c(self), 225 | as_c(out_ar), 226 | C.c_int(self.size)) 227 | return FourierSeries(out_ar,self.header.newHeader()) 228 | 229 | 230 | def reconProf(self,freq,nharms=32): 231 | """Reconstruct the time domain pulse profile from a signal and its harmonics. 232 | 233 | :param freq: frequency of signal to reconstruct 234 | :type freq: float 235 | 236 | :param nharms: number of harmonics to use in reconstruction (def=32) 237 | :type nharms: int 238 | 239 | :return: a pulse profile 240 | :rtype: :class:`sigpyproc.FoldedData.Profile` 241 | """ 242 | bin_ = freq*self.header.tobs 243 | real_ids = np.array([int(round(ii*2*bin_)) for ii in range(1,nharms+1)]) 244 | imag_ids = real_ids+1 245 | harms = self[real_ids] + 1j*self[imag_ids] 246 | harm_ar = np.hstack((harms,np.conj(harms[1:][::-1]))) 247 | return Profile(abs(np.fft.ifft(harm_ar))) 248 | 249 | def toFile(self,filename=None): 250 | """Write spectrum to file in sigpyproc format. 251 | 252 | :param filename: name of file to write to (def=``basename.spec``) 253 | :type filename: str 254 | 255 | :return: name of file written to 256 | :rtype: :func:`str` 257 | """ 258 | if filename is None: 259 | filename = "%s.spec"%(self.header.basename) 260 | outfile = self.header.prepOutfile(filename,nbits=32) 261 | self.tofile(outfile) 262 | return outfile.name 263 | 264 | def toFFTFile(self,basename=None): 265 | """Write spectrum to file in sigpyproc format. 266 | 267 | :param basename: basename of .fft and .inf file to be written 268 | :type filename: str 269 | 270 | :return: name of files written to 271 | :rtype: :func:`tuple` of :func:`str` 272 | """ 273 | if basename is None: basename = self.header.basename 274 | self.header.makeInf(outfile="%s.inf"%(basename)) 275 | fftfile = File("%s.fft"%(basename),"w+") 276 | self.tofile(fftfile) 277 | return "%s.fft"%(basename),"%s.inf"%(basename) 278 | 279 | from sigpyproc.TimeSeries import TimeSeries 280 | from sigpyproc.FoldedData import Profile 281 | -------------------------------------------------------------------------------- /sigpyproc/Readers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sigpyproc.HeaderParams as conf 4 | import numpy as np 5 | from inspect import stack as istack 6 | from struct import unpack 7 | from sys import stdout 8 | from sigpyproc.Utils import File 9 | from sigpyproc.Header import Header 10 | from sigpyproc.Filterbank import Filterbank,FilterbankBlock 11 | from sigpyproc.TimeSeries import TimeSeries 12 | from sigpyproc.FourierSeries import FourierSeries 13 | 14 | class FilReader(Filterbank): 15 | """Class to handle the reading of sigproc format filterbank files 16 | 17 | :param filename: name of filterbank file 18 | :type filename: :func:`str` 19 | 20 | .. note:: 21 | 22 | To be considered as a Sigproc format filterbank file the header must only 23 | contain keywords found in the ``HeaderParams.header_keys`` dictionary. 24 | """ 25 | def __init__(self,filename): 26 | self.filename = filename 27 | self.header = parseSigprocHeader(self.filename) 28 | self._file = File(filename,"r",self.header.nbits) 29 | self.itemsize = np.dtype(self.header.dtype).itemsize 30 | if self.header.nbits in [1,2,4]: 31 | self.bitfact = 8/self.header.nbits 32 | else: 33 | self.bitfact = 1 34 | self.sampsize = self.header.nchans*self.itemsize/self.bitfact 35 | super(FilReader,self).__init__() 36 | 37 | def readBlock(self,start,nsamps): 38 | """Read a block of filterbank data. 39 | 40 | :param start: first time sample of the block to be read 41 | :type start: int 42 | 43 | :param nsamps: number of samples in the block (i.e. block will be nsamps*nchans in size) 44 | :type nsamps: int 45 | 46 | :return: 2-D array of filterbank data 47 | :rtype: :class:`~sigpyproc.Filterbank.FilterbankBlock` 48 | """ 49 | self._file.seek(self.header.hdrlen+start*self.sampsize) 50 | data = self._file.cread(self.header.nchans*nsamps) 51 | nsamps_read = data.size / self.header.nchans 52 | data = data.reshape(nsamps_read, self.header.nchans).transpose() 53 | start_mjd = self.header.mjdAfterNsamps(start) 54 | new_header = self.header.newHeader({'tstart':start_mjd}) 55 | return FilterbankBlock(data,new_header) 56 | 57 | def readPlan(self,gulp,skipback=0,start=0,nsamps=None,verbose=True): 58 | """A generator used to perform filterbank reading. 59 | 60 | :param gulp: number of samples in each read 61 | :type gulp: int 62 | 63 | :param skipback: number of samples to skip back after each read (def=0) 64 | :type skipback: int 65 | 66 | :param start: first sample to read from filterbank (def=start of file) 67 | :type start: int 68 | 69 | :param nsamps: total number samples to read (def=end of file) 70 | :type nsamps: int 71 | 72 | :param verbose: flag for display of reading plan information (def=True) 73 | :type verbose: bool 74 | 75 | :return: An generator that can read through the file. 76 | :rtype: generator object 77 | 78 | .. note:: 79 | 80 | For each read, the generator yields a tuple ``x``, where: 81 | 82 | * ``x[0]`` is the number of samples read 83 | * ``x[1]`` is the index of the read (i.e. ``x[1]=0`` is the first read) 84 | * ``x[2]`` is a 1-D numpy array containing the data that was read 85 | 86 | The normal calling syntax for this is function is: 87 | 88 | .. code-block:: python 89 | 90 | for nsamps, ii, data in self.readPlan(*args,**kwargs): 91 | # do something 92 | 93 | where data always has contains ``nchans*nsamps`` points. 94 | 95 | """ 96 | 97 | if nsamps is None: 98 | nsamps = self.header.nsamples-start 99 | if nsamps= gulp: 104 | raise ValueError,"readsamps must be > skipback value" 105 | self._file.seek(self.header.hdrlen+start*self.sampsize) 106 | nreads = nsamps//(gulp-skipback) 107 | lastread = nsamps-(nreads*(gulp-skipback)) 108 | if lastread 2 | #include 3 | #include 4 | 5 | double genrand_real3(); 6 | double gsl_sf_erf_Q(double x); // GNU function -- see copyright below. 7 | void init_by_array(unsigned long init_key[], int key_length); 8 | double normDist(); 9 | 10 | void seed(){ 11 | unsigned long idum[4]; 12 | idum[0] = (unsigned long)time( NULL ); 13 | idum[1] = (unsigned long)time( NULL ) + 1; 14 | idum[2] = (unsigned long)time( NULL ) + 3; 15 | idum[3] = (unsigned long)time( NULL ) + 1982; 16 | unsigned long length = 4; 17 | init_by_array( idum, length ); 18 | } 19 | 20 | //#---convert Mersenne twister generated numbers to a gaussian distribution-----# 21 | double normDist(){ 22 | 23 | double fac, rsq, v1, v2; 24 | static int iset = 0; 25 | static double gset; 26 | if( iset == 0 ){ 27 | do{ 28 | v1 = 2.0 * genrand_real3() - 1.0; 29 | v2 = 2.0 * genrand_real3() - 1.0; 30 | rsq = v1 * v1 + v2 * v2; 31 | }while( rsq >= 1.0 || rsq == 0.0 ); 32 | fac = sqrt( -2.0 * log( rsq ) / rsq ); 33 | gset = v1 * fac; 34 | iset = 1; 35 | return( v2* fac ); 36 | }else{ 37 | iset = 0; 38 | return( gset ); 39 | } 40 | } 41 | 42 | // Below is the Mersenne twister code by Takuji Nishimura and Makoto 43 | // Matsumoto, which comes with the following copyright information: 44 | 45 | /* 46 | A C-program for MT19937, with initialization improved 2002/1/26. 47 | Coded by Takuji Nishimura and Makoto Matsumoto. 48 | 49 | Before using, initialize the state by using init_genrand(seed) 50 | or init_by_array(init_key, key_length). 51 | 52 | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, 53 | All rights reserved. 54 | 55 | Redistribution and use in source and binary forms, with or without 56 | modification, are permitted provided that the following conditions 57 | are met: 58 | 59 | 1. Redistributions of source code must retain the above copyright 60 | notice, this list of conditions and the following disclaimer. 61 | 62 | 2. Redistributions in binary form must reproduce the above copyright 63 | notice, this list of conditions and the following disclaimer in the 64 | documentation and/or other materials provided with the distribution. 65 | 66 | 3. The names of its contributors may not be used to endorse or promote 67 | products derived from this software without specific prior written 68 | permission. 69 | 70 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 71 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 72 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 73 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 74 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 75 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 76 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 77 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 78 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 79 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 80 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 81 | 82 | 83 | Any feedback is very welcome. 84 | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html 85 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) 86 | */ 87 | 88 | /* Period parameters */ 89 | #define N 624 90 | #define M 397 91 | #define MATRIX_A 0x9908b0dfUL /* constant vector a */ 92 | #define UPPER_MASK 0x80000000UL /* most significant w-r bits */ 93 | #define LOWER_MASK 0x7fffffffUL /* least significant r bits */ 94 | 95 | static unsigned long mt[N]; /* the array for the state vector */ 96 | static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ 97 | 98 | /* initializes mt[N] with a seed */ 99 | void init_genrand(unsigned long s) 100 | { 101 | mt[0]= s & 0xffffffffUL; 102 | for (mti=1; mti> 30)) + mti); 105 | /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ 106 | /* In the previous versions, MSBs of the seed affect */ 107 | /* only MSBs of the array mt[]. */ 108 | /* 2002/01/09 modified by Makoto Matsumoto */ 109 | mt[mti] &= 0xffffffffUL; 110 | /* for >32 bit machines */ 111 | } 112 | } 113 | 114 | /* initialize by an array with array-length */ 115 | /* init_key is the array for initializing keys */ 116 | /* key_length is its length */ 117 | /* slight change for C++, 2004/2/26 */ 118 | void init_by_array(unsigned long init_key[], int key_length) 119 | { 120 | int i, j, k; 121 | init_genrand(19650218UL); 122 | i=1; j=0; 123 | k = (N>key_length ? N : key_length); 124 | for (; k; k--) { 125 | mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) 126 | + init_key[j] + j; /* non linear */ 127 | mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ 128 | i++; j++; 129 | if (i>=N) { mt[0] = mt[N-1]; i=1; } 130 | if (j>=key_length) j=0; 131 | } 132 | for (k=N-1; k; k--) { 133 | mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) 134 | - i; /* non linear */ 135 | mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ 136 | i++; 137 | if (i>=N) { mt[0] = mt[N-1]; i=1; } 138 | } 139 | 140 | mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ 141 | } 142 | 143 | /* generates a random number on [0,0xffffffff]-interval */ 144 | unsigned long genrand_int32(void) 145 | { 146 | unsigned long y; 147 | static unsigned long mag01[2]={0x0UL, MATRIX_A}; 148 | /* mag01[x] = x * MATRIX_A for x=0,1 */ 149 | 150 | if (mti >= N) { /* generate N words at one time */ 151 | int kk; 152 | 153 | if (mti == N+1) /* if init_genrand() has not been called, */ 154 | init_genrand(5489UL); /* a default initial seed is used */ 155 | 156 | for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; 159 | } 160 | for (;kk> 1) ^ mag01[y & 0x1UL]; 163 | } 164 | y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); 165 | mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; 166 | 167 | mti = 0; 168 | } 169 | 170 | y = mt[mti++]; 171 | 172 | /* Tempering */ 173 | y ^= (y >> 11); 174 | y ^= (y << 7) & 0x9d2c5680UL; 175 | y ^= (y << 15) & 0xefc60000UL; 176 | y ^= (y >> 18); 177 | 178 | return y; 179 | } 180 | 181 | /* generates a random number on (0,1)-real-interval */ 182 | double genrand_real3(void) 183 | { 184 | return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0); 185 | /* divided by 2^32 */ 186 | } 187 | 188 | // Below is the GNU scientific library code for calculation of the 189 | // normal cumulative density function, used in the calculation of the 190 | // HI distance limits. 191 | 192 | /* Author: B. Gough and G. Jungman */ 193 | #define GSL_DBL_EPSILON 2.2204460492503131e-16 194 | 195 | /* specfunc/chebyshev.h 196 | * 197 | * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman 198 | * 199 | * This program is free software; you can redistribute it and/or modify 200 | * it under the terms of the GNU General Public License as published by 201 | * the Free Software Foundation; either version 3 of the License, or (at 202 | * your option) any later version. 203 | * 204 | * This program is distributed in the hope that it will be useful, but 205 | * WITHOUT ANY WARRANTY; without even the implied warranty of 206 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 207 | * General Public License for more details. 208 | * 209 | * You should have received a copy of the GNU General Public License 210 | * along with this program; if not, write to the Free Software 211 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 212 | */ 213 | 214 | /* data for a Chebyshev series over a given interval */ 215 | 216 | struct cheb_series_struct { 217 | double * c; /* coefficients */ 218 | int order; /* order of expansion */ 219 | double a; /* lower interval point */ 220 | double b; /* upper interval point */ 221 | int order_sp; /* effective single precision order */ 222 | }; 223 | typedef struct cheb_series_struct cheb_series; 224 | 225 | /* specfunc/gsl_sf_result.h 226 | * 227 | * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman 228 | * 229 | * This program is free software; you can redistribute it and/or modify 230 | * it under the terms of the GNU General Public License as published by 231 | * the Free Software Foundation; either version 3 of the License, or (at 232 | * your option) any later version. 233 | * 234 | * This program is distributed in the hope that it will be useful, but 235 | * WITHOUT ANY WARRANTY; without even the implied warranty of 236 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 237 | * General Public License for more details. 238 | * 239 | * You should have received a copy of the GNU General Public License 240 | * along with this program; if not, write to the Free Software 241 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 242 | */ 243 | 244 | /* Author: G. Jungman */ 245 | 246 | #ifndef __GSL_SF_RESULT_H__ 247 | #define __GSL_SF_RESULT_H__ 248 | 249 | #undef __BEGIN_DECLS 250 | #undef __END_DECLS 251 | #ifdef __cplusplus 252 | # define __BEGIN_DECLS extern "C" { 253 | # define __END_DECLS } 254 | #else 255 | # define __BEGIN_DECLS /* empty */ 256 | # define __END_DECLS /* empty */ 257 | #endif 258 | 259 | __BEGIN_DECLS 260 | 261 | struct gsl_sf_result_struct { 262 | double val; 263 | double err; 264 | }; 265 | typedef struct gsl_sf_result_struct gsl_sf_result; 266 | 267 | #define GSL_SF_RESULT_SET(r,v,e) do { (r)->val=(v); (r)->err=(e); } while(0) 268 | 269 | 270 | struct gsl_sf_result_e10_struct { 271 | double val; 272 | double err; 273 | int e10; 274 | }; 275 | typedef struct gsl_sf_result_e10_struct gsl_sf_result_e10; 276 | 277 | 278 | int gsl_sf_result_smash_e(const gsl_sf_result_e10 * re, gsl_sf_result * r); 279 | 280 | 281 | __END_DECLS 282 | 283 | #endif /* __GSL_SF_RESULT_H__ */ 284 | 285 | /* specfunc/erfc.c 286 | * 287 | * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 Gerard Jungman 288 | * 289 | * This program is free software; you can redistribute it and/or modify 290 | * it under the terms of the GNU General Public License as published by 291 | * the Free Software Foundation; either version 3 of the License, or (at 292 | * your option) any later version. 293 | * 294 | * This program is distributed in the hope that it will be useful, but 295 | * WITHOUT ANY WARRANTY; without even the implied warranty of 296 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 297 | * General Public License for more details. 298 | * 299 | * You should have received a copy of the GNU General Public License 300 | * along with this program; if not, write to the Free Software 301 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 302 | */ 303 | 304 | /* Author: J. Theiler (modifications by G. Jungman) */ 305 | 306 | /* 307 | * See Hart et al, Computer Approximations, John Wiley and Sons, New York (1968) 308 | * (This applies only to the erfc8 stuff, which is the part 309 | * of the original code that survives. I have replaced much of 310 | * the other stuff with Chebyshev fits. These are simpler and 311 | * more precise than the original approximations. [GJ]) 312 | */ 313 | 314 | #define EVAL_RESULT(fn) \ 315 | gsl_sf_result result; \ 316 | fn; \ 317 | return result.val; 318 | 319 | /* Chebyshev fit for erfc((t+1)/2), -1 < t < 1 320 | */ 321 | static double erfc_xlt1_data[20] = { 322 | 1.06073416421769980345174155056, 323 | -0.42582445804381043569204735291, 324 | 0.04955262679620434040357683080, 325 | 0.00449293488768382749558001242, 326 | -0.00129194104658496953494224761, 327 | -0.00001836389292149396270416979, 328 | 0.00002211114704099526291538556, 329 | -5.23337485234257134673693179020e-7, 330 | -2.78184788833537885382530989578e-7, 331 | 1.41158092748813114560316684249e-8, 332 | 2.72571296330561699984539141865e-9, 333 | -2.06343904872070629406401492476e-10, 334 | -2.14273991996785367924201401812e-11, 335 | 2.22990255539358204580285098119e-12, 336 | 1.36250074650698280575807934155e-13, 337 | -1.95144010922293091898995913038e-14, 338 | -6.85627169231704599442806370690e-16, 339 | 1.44506492869699938239521607493e-16, 340 | 2.45935306460536488037576200030e-18, 341 | -9.29599561220523396007359328540e-19 342 | }; 343 | static cheb_series erfc_xlt1_cs = { 344 | erfc_xlt1_data, 345 | 19, 346 | -1, 1, 347 | 12 348 | }; 349 | 350 | /* Chebyshev fit for erfc(x) exp(x^2), 1 < x < 5, x = 2t + 3, -1 < t < 1 351 | */ 352 | static double erfc_x15_data[25] = { 353 | 0.44045832024338111077637466616, 354 | -0.143958836762168335790826895326, 355 | 0.044786499817939267247056666937, 356 | -0.013343124200271211203618353102, 357 | 0.003824682739750469767692372556, 358 | -0.001058699227195126547306482530, 359 | 0.000283859419210073742736310108, 360 | -0.000073906170662206760483959432, 361 | 0.000018725312521489179015872934, 362 | -4.62530981164919445131297264430e-6, 363 | 1.11558657244432857487884006422e-6, 364 | -2.63098662650834130067808832725e-7, 365 | 6.07462122724551777372119408710e-8, 366 | -1.37460865539865444777251011793e-8, 367 | 3.05157051905475145520096717210e-9, 368 | -6.65174789720310713757307724790e-10, 369 | 1.42483346273207784489792999706e-10, 370 | -3.00141127395323902092018744545e-11, 371 | 6.22171792645348091472914001250e-12, 372 | -1.26994639225668496876152836555e-12, 373 | 2.55385883033257575402681845385e-13, 374 | -5.06258237507038698392265499770e-14, 375 | 9.89705409478327321641264227110e-15, 376 | -1.90685978789192181051961024995e-15, 377 | 3.50826648032737849245113757340e-16 378 | }; 379 | static cheb_series erfc_x15_cs = { 380 | erfc_x15_data, 381 | 24, 382 | -1, 1, 383 | 16 384 | }; 385 | 386 | /* Chebyshev fit for erfc(x) x exp(x^2), 5 < x < 10, x = (5t + 15)/2, -1 < t < 1 387 | */ 388 | static double erfc_x510_data[20] = { 389 | 1.11684990123545698684297865808, 390 | 0.003736240359381998520654927536, 391 | -0.000916623948045470238763619870, 392 | 0.000199094325044940833965078819, 393 | -0.000040276384918650072591781859, 394 | 7.76515264697061049477127605790e-6, 395 | -1.44464794206689070402099225301e-6, 396 | 2.61311930343463958393485241947e-7, 397 | -4.61833026634844152345304095560e-8, 398 | 8.00253111512943601598732144340e-9, 399 | -1.36291114862793031395712122089e-9, 400 | 2.28570483090160869607683087722e-10, 401 | -3.78022521563251805044056974560e-11, 402 | 6.17253683874528285729910462130e-12, 403 | -9.96019290955316888445830597430e-13, 404 | 1.58953143706980770269506726000e-13, 405 | -2.51045971047162509999527428316e-14, 406 | 3.92607828989125810013581287560e-15, 407 | -6.07970619384160374392535453420e-16, 408 | 9.12600607264794717315507477670e-17 409 | }; 410 | static cheb_series erfc_x510_cs = { 411 | erfc_x510_data, 412 | 19, 413 | -1, 1, 414 | 12 415 | }; 416 | 417 | static double erfc8_sum(double x) 418 | { 419 | /* estimates erfc(x) valid for 8 < x < 100 */ 420 | /* This is based on index 5725 in Hart et al */ 421 | 422 | static double P[] = { 423 | 2.97886562639399288862, 424 | 7.409740605964741794425, 425 | 6.1602098531096305440906, 426 | 5.019049726784267463450058, 427 | 1.275366644729965952479585264, 428 | 0.5641895835477550741253201704 429 | }; 430 | static double Q[] = { 431 | 3.3690752069827527677, 432 | 9.608965327192787870698, 433 | 17.08144074746600431571095, 434 | 12.0489519278551290360340491, 435 | 9.396034016235054150430579648, 436 | 2.260528520767326969591866945, 437 | 1.0 438 | }; 439 | double num=0.0, den=0.0; 440 | int i; 441 | 442 | num = P[5]; 443 | for (i=4; i>=0; --i) { 444 | num = x*num + P[i]; 445 | } 446 | den = Q[6]; 447 | for (i=5; i>=0; --i) { 448 | den = x*den + Q[i]; 449 | } 450 | 451 | return num/den; 452 | } 453 | 454 | inline 455 | static double erfc8(double x) 456 | { 457 | double e; 458 | e = erfc8_sum(x); 459 | e *= exp(-x*x); 460 | return e; 461 | } 462 | 463 | int cheb_eval_e(const cheb_series * cs, 464 | const double x, 465 | gsl_sf_result * result) 466 | { 467 | int j; 468 | double d = 0.0; 469 | double dd = 0.0; 470 | 471 | double y = (2.0*x - cs->a - cs->b) / (cs->b - cs->a); 472 | double y2 = 2.0 * y; 473 | 474 | double e = 0.0; 475 | 476 | for(j = cs->order; j>=1; j--) { 477 | double temp = d; 478 | d = y2*d - dd + cs->c[j]; 479 | e += fabs(y2*temp) + fabs(dd) + fabs(cs->c[j]); 480 | dd = temp; 481 | } 482 | 483 | { 484 | double temp = d; 485 | d = y*d - dd + 0.5 * cs->c[0]; 486 | e += fabs(y*temp) + fabs(dd) + 0.5 * fabs(cs->c[0]); 487 | } 488 | 489 | result->val = d; 490 | result->err = GSL_DBL_EPSILON * e + fabs(cs->c[cs->order]); 491 | 492 | return 1; 493 | } 494 | 495 | /*-*-*-*-*-*-*-*-*-*-*-* Functions with Error Codes *-*-*-*-*-*-*-*-*-*-*-*/ 496 | 497 | int gsl_sf_erfc_e(double x, gsl_sf_result * result) 498 | { 499 | const double ax = fabs(x); 500 | double e_val, e_err; 501 | 502 | /* CHECK_POINTER(result) */ 503 | 504 | if(ax <= 1.0) { 505 | double t = 2.0*ax - 1.0; 506 | gsl_sf_result c; 507 | cheb_eval_e(&erfc_xlt1_cs, t, &c); 508 | e_val = c.val; 509 | e_err = c.err; 510 | } 511 | else if(ax <= 5.0) { 512 | double ex2 = exp(-x*x); 513 | double t = 0.5*(ax-3.0); 514 | gsl_sf_result c; 515 | cheb_eval_e(&erfc_x15_cs, t, &c); 516 | e_val = ex2 * c.val; 517 | e_err = ex2 * (c.err + 2.0*fabs(x)*GSL_DBL_EPSILON); 518 | } 519 | else if(ax < 10.0) { 520 | double exterm = exp(-x*x) / ax; 521 | double t = (2.0*ax - 15.0)/5.0; 522 | gsl_sf_result c; 523 | cheb_eval_e(&erfc_x510_cs, t, &c); 524 | e_val = exterm * c.val; 525 | e_err = exterm * (c.err + 2.0*fabs(x)*GSL_DBL_EPSILON + GSL_DBL_EPSILON); 526 | } 527 | else { 528 | e_val = erfc8(ax); 529 | e_err = (x*x + 1.0) * GSL_DBL_EPSILON * fabs(e_val); 530 | } 531 | 532 | if(x < 0.0) { 533 | result->val = 2.0 - e_val; 534 | result->err = e_err; 535 | result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val); 536 | } 537 | else { 538 | result->val = e_val; 539 | result->err = e_err; 540 | result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val); 541 | } 542 | 543 | return 1; 544 | } 545 | 546 | int gsl_sf_erf_Q_e(double x, gsl_sf_result * result){ 547 | gsl_sf_result result_erfc; 548 | int stat = gsl_sf_erfc_e(x/M_SQRT2, &result_erfc); 549 | result->val = 0.5 * result_erfc.val; 550 | result->err = 0.5 * result_erfc.err; 551 | result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val); 552 | return stat; 553 | } 554 | 555 | double gsl_sf_erf_Q(double x){ 556 | EVAL_RESULT(gsl_sf_erf_Q_e(x, &result)); 557 | } 558 | -------------------------------------------------------------------------------- /sigpyproc/Filterbank.py: -------------------------------------------------------------------------------- 1 | from numpy.ctypeslib import as_ctypes as as_c 2 | from sigpyproc.Utils import rollArray 3 | from sigpyproc.FoldedData import FoldedData 4 | import ctypes as C 5 | import numpy as np 6 | 7 | from .ctype_helper import load_lib 8 | lib32 = load_lib("libSigPyProc32.so") 9 | lib8 = load_lib("libSigPyProc8.so") 10 | 11 | class Filterbank(object): 12 | """Class exporting methods for the manipulation of frequency-major 13 | order pulsar data. 14 | 15 | .. note:: 16 | 17 | The Filterbank class should never be instantiated directly. Instead it 18 | should be inherited by data reading classes. 19 | """ 20 | def __init__(self): 21 | if self.header.nbits == 32: 22 | self.lib = lib32 #if 32-bit data select 32-bit library 23 | else: 24 | self.lib = lib8 #if 8-bit data select 8-bit library 25 | self.chan_means = None 26 | self.chan_stdevs = None 27 | self.chan_maxima = None 28 | self.chan_minima = None 29 | 30 | 31 | def setNthreads(self,nthreads=None): 32 | """Set the number of threads available to OpenMP. 33 | 34 | :param nthreads: number of threads to use (def = 4) 35 | :type nthreads: int 36 | """ 37 | if nthreads is None: 38 | nthreads=4 39 | self.lib.omp_set_num_threads(nthreads) 40 | 41 | def collapse(self,gulp=512,start=0,nsamps=None): 42 | """Sum across all frequencies for each time sample. 43 | 44 | :param gulp: number of samples in each read 45 | :type gulp: int 46 | 47 | :return: A zero-DM time series 48 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 49 | """ 50 | if nsamps is None: 51 | size = self.header.nsamples-start 52 | else: 53 | size = nsamps 54 | timar = np.zeros(size,dtype="float32") 55 | timar_c = as_c(timar) 56 | for nsamps,ii,data in self.readPlan(gulp,start=start,nsamps=nsamps): 57 | self.lib.getTim(as_c(data), 58 | timar_c, 59 | C.c_int(self.header.nchans), 60 | C.c_int(nsamps), 61 | C.c_int(ii*gulp)) 62 | return TimeSeries(timar,self.header.newHeader({"nchans":1,"refdm":0.0})) 63 | 64 | def invertFreq(self,gulp=512,start=0,nsamps=None,filename=None,back_compatible=True): 65 | """Invert the frequency ordering of the data and write new data to a new file. 66 | 67 | :param gulp: number of samples in each read 68 | :type gulp: int 69 | 70 | :param start: start sample 71 | :type start: int 72 | 73 | :param nsamps: number of samples in split 74 | :type nsamps: int 75 | 76 | :param filename: name of output file (defaults to ``basename_inverted.fil``) 77 | :type filename: string 78 | 79 | :param back_compatible: sigproc compatibility flag (legacy code) 80 | :type back_compatible: bool 81 | 82 | :return: name of output file 83 | :return type: :func:`str` 84 | """ 85 | if filename is None: 86 | filename = "%s_inverted.fil"%(self.header.basename) 87 | 88 | if nsamps is None: 89 | size = self.header.nsamples-start 90 | else: 91 | size = nsamps 92 | out_ar = np.empty(size*self.header.nchans,dtype=self.header.dtype) 93 | 94 | if self.header.foff >= 0.0: 95 | sign = 1.0 96 | else: 97 | sign = -1.0 98 | changes = {"fch1":self.header.fch1+sign*(self.header.nchans-1)*self.header.foff, 99 | "foff":self.header.foff*sign*(-1.0)} 100 | #NB bandwidth is +ive by default 101 | out_file = self.header.prepOutfile(filename, changes, 102 | nbits=self.header.nbits, 103 | back_compatible=back_compatible) 104 | for nsamps,ii,data in self.readPlan(gulp,start=start,nsamps=nsamps): 105 | self.lib.invertFreq(as_c(data), 106 | as_c(out_ar), 107 | C.c_int(self.header.nchans), 108 | C.c_int(nsamps)) 109 | out_file.cwrite(out_ar[:nsamps*self.header.nchans]) 110 | out_file.close() 111 | return out_file.name 112 | 113 | def bandpass(self,gulp=512): 114 | """Sum across each time sample for all frequencies. 115 | 116 | :param gulp: number of samples in each read 117 | :type gulp: int 118 | 119 | :return: the bandpass of the data 120 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 121 | """ 122 | bpass_ar = np.zeros(self.header.nchans,dtype="float32") 123 | bpass_ar_c = as_c(bpass_ar) 124 | for nsamps,ii,data in self.readPlan(gulp): 125 | self.lib.getBpass(as_c(data), 126 | bpass_ar_c, 127 | C.c_int(self.header.nchans), 128 | C.c_int(nsamps)) 129 | return TimeSeries(bpass_ar,self.header.newHeader({"nchans":1})) 130 | 131 | def dedisperse(self,dm,gulp=10000): 132 | """Dedisperse the data to a time series. 133 | 134 | :param dm: dispersion measure to dedisperse to 135 | :type dm: float 136 | 137 | :param gulp: number of samples in each read 138 | :type gulp: int 139 | 140 | :return: a dedispersed time series 141 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 142 | 143 | .. note:: 144 | 145 | If gulp < maximum dispersion delay, gulp is taken to be twice the maximum dispersion delay. 146 | 147 | """ 148 | chan_delays = self.header.getDMdelays(dm) 149 | chan_delays_c = as_c(chan_delays) 150 | max_delay = int(chan_delays.max()) 151 | gulp = max(2*max_delay,gulp) 152 | tim_len = self.header.nsamples-max_delay 153 | tim_ar = np.zeros(tim_len,dtype="float32") 154 | tim_ar_c = as_c(tim_ar) 155 | for nsamps,ii,data in self.readPlan(gulp,skipback=max_delay): 156 | self.lib.dedisperse(as_c(data), 157 | tim_ar_c, 158 | chan_delays_c, 159 | C.c_int(max_delay), 160 | C.c_int(self.header.nchans), 161 | C.c_int(nsamps), 162 | C.c_int(ii*(gulp-max_delay))) 163 | return TimeSeries(tim_ar,self.header.newHeader({"nchans":1,"refdm":dm})) 164 | 165 | def subband(self,dm,nsub,filename=None,gulp=10000): 166 | """Produce a set of dedispersed subbands from the data. 167 | 168 | :param dm: the DM of the subbands 169 | :type dm: float 170 | 171 | :param nsub: the number of subbands to produce 172 | :type nsub: int 173 | 174 | :param filename: output file name of subbands (def=basename_DM.subbands) 175 | :type filename: :func:`str` 176 | 177 | :param gulp: number of samples in each read 178 | :type gulp: int 179 | 180 | :return: name of output subbands file 181 | :rtype: :func:`str` 182 | """ 183 | 184 | subfactor = self.header.nchans/nsub 185 | chan_delays = self.header.getDMdelays(dm) 186 | chan_delays_c = as_c(chan_delays) 187 | max_delay = int(chan_delays.max()) 188 | gulp = max(2*max_delay,gulp) 189 | out_ar = np.empty((gulp-max_delay)*nsub,dtype="float32") #must be memset to zero in c code 190 | out_ar_c = as_c(out_ar) 191 | new_foff = self.header.foff*self.header.nchans/nsub 192 | new_fch1 = self.header.ftop-new_foff/2. 193 | chan_to_sub = np.arange(self.header.nchans,dtype="int32")/subfactor 194 | chan_to_sub_c = as_c(chan_to_sub) 195 | changes = {"fch1" :new_fch1, 196 | "foff" :new_foff, 197 | "refdm" :dm, 198 | "nchans":nsub, 199 | "nbits" :32} 200 | if filename is None: 201 | filename = "%s_DM%06.2f.subbands"%(self.header.basename,dm) 202 | out_file = self.header.prepOutfile(filename, changes, nbits=32, 203 | back_compatible=True) 204 | 205 | for nsamps,ii,data in self.readPlan(gulp,skipback=max_delay): 206 | self.lib.subband(as_c(data), 207 | out_ar_c, 208 | chan_delays_c, 209 | chan_to_sub_c, 210 | C.c_int(max_delay), 211 | C.c_int(self.header.nchans), 212 | C.c_int(nsub), 213 | C.c_int(nsamps)) 214 | out_file.cwrite(out_ar[:(nsamps-max_delay)*nsub]) 215 | return filename 216 | 217 | def upTo8bit(self,filename=None,gulp=512,back_compatible=True): 218 | """Convert 1-,2- or 4-bit data to 8-bit data and write to file. 219 | 220 | :param filename: name of file to write to (defaults to ``basename_8bit.fil`` ) 221 | :type filename: str 222 | 223 | :param gulp: number of samples in each read 224 | :type gulp: int 225 | 226 | :param back_compatible: sigproc compatibility flag 227 | :type back_compatible: bool 228 | 229 | :return: name of output file 230 | :rtype: :func:`str` 231 | 232 | """ 233 | if filename is None: 234 | filename = "%s_8bit.fil"%(self.header.basename) 235 | 236 | out_file = self.header.prepOutfile(filename,{"nbits":8},nbits=8,back_compatible=back_compatible) 237 | for nsamps,ii,data in self.readPlan(gulp): 238 | out_file.cwrite(data) 239 | return out_file.name 240 | 241 | def applyChannelMask(self,chanmask,outfilename=None,gulp=512,back_compatible=True): 242 | """Set the data in the given channels to zero. 243 | 244 | :param outfilename: name of the output filterbank file 245 | :type outfilename: str 246 | 247 | :param chanmask: binary channel mask (0 for bad channel, 1 for good) 248 | :type chanmask: list 249 | 250 | :param gulp: number of samples in each read 251 | :type gulp: int 252 | 253 | :param back_compatible: sigproc compatibility flag 254 | :type back_compatible: bool 255 | 256 | :return: outfile name 257 | :rtype: str 258 | """ 259 | if outfilename is None: 260 | outfilename = "%s_masked.fil"%(self.header.basename) 261 | mask = np.array(chanmask).astype("ubyte") 262 | out_file = self.header.prepOutfile(outfilename,back_compatible=back_compatible) 263 | for nsamps,ii,data in self.readPlan(gulp): 264 | self.lib.maskChannels(as_c(data), 265 | as_c(mask), 266 | C.c_int(self.header.nchans), 267 | C.c_int(nsamps)) 268 | out_file.cwrite(data) 269 | return out_file.name 270 | 271 | def downsample(self,tfactor=1,ffactor=1,gulp=512,filename=None,back_compatible=True): 272 | """Downsample data in time and/or frequency and write to file. 273 | 274 | :param tfactor: factor by which to downsample in time 275 | :type tfactor: int 276 | 277 | :param ffactor: factor by which to downsample in frequency 278 | :type ffactor: int 279 | 280 | :param gulp: number of samples in each read 281 | :type gulp: int 282 | 283 | :param filename: name of file to write to (defaults to ``basename_tfactor_ffactor.fil``) 284 | :type filename: str 285 | 286 | :param back_compatible: sigproc compatibility flag (legacy code) 287 | :type back_compatible: bool 288 | 289 | :return: output file name 290 | :rtype: :func:`str` 291 | """ 292 | if filename is None: 293 | filename = "%s_f%d_t%d.fil"%(self.header.basename,ffactor,tfactor) 294 | if not self.header.nchans%ffactor == 0: 295 | raise ValueError,"Bad frequency factor given" 296 | if not gulp%tfactor == 0: 297 | raise ValueError,"Gulp must be a multiple of tfactor" 298 | out_file = self.header.prepOutfile(filename, 299 | {"tsamp":self.header.tsamp*tfactor, 300 | "nchans":self.header.nchans/ffactor, 301 | "foff":self.header.foff*ffactor}, 302 | back_compatible=back_compatible) 303 | 304 | write_ar = np.zeros(gulp*self.header.nchans/ffactor/tfactor,dtype=self.header.dtype) 305 | write_ar_c = as_c(write_ar) 306 | for nsamps,ii,data in self.readPlan(gulp): 307 | self.lib.downsample(as_c(data), 308 | write_ar_c, 309 | C.c_int(tfactor), 310 | C.c_int(ffactor), 311 | C.c_int(self.header.nchans), 312 | C.c_int(nsamps)) 313 | out_file.cwrite(write_ar) 314 | return out_file.name 315 | 316 | def fold(self,period,dm,accel=0,nbins=50,nints=32,nbands=32,gulp=10000): 317 | """Fold data into discrete phase, subintegration and subband bins. 318 | 319 | :param period: period in seconds to fold with 320 | :type period: float 321 | 322 | :param dm: dispersion measure to dedisperse to 323 | :type dm: float 324 | 325 | :param accel: acceleration in m/s/s to fold with 326 | :type accel: float 327 | 328 | :param nbins: number of phase bins in output 329 | :type nbins: int 330 | 331 | :param nints: number of subintegrations in output 332 | :type nints: int 333 | 334 | :param nbands: number of subbands in output 335 | :type nbands: int 336 | 337 | :param gulp: number of samples in each read 338 | :type gulp: int 339 | 340 | :return: 3 dimensional data cube 341 | :rtype: :class:`~sigpyproc.FoldedData.FoldedData` 342 | 343 | .. note:: 344 | 345 | If gulp < maximum dispersion delay, gulp is taken to be twice the maximum dispersion delay. 346 | 347 | """ 348 | if np.modf(period/self.header.tsamp)[0]<0.001: 349 | print "WARNING: Foldng interval is an integer multiple of the sampling time" 350 | if nbins > period/self.header.tsamp: 351 | print "WARNING: Number of phase bins is greater than period/sampling time" 352 | if (self.header.nsamples*self.header.nchans)/(nbands*nints*nbins) < 10: 353 | raise ValueError,"nbands x nints x nbins is too large." 354 | nbands = min(nbands,self.header.nchans) 355 | chan_delays = self.header.getDMdelays(dm) 356 | chan_delays_c = as_c(chan_delays) 357 | max_delay = int(chan_delays.max()) 358 | gulp = max(2*max_delay,gulp) 359 | fold_ar = np.zeros(nbins*nints*nbands,dtype="float32") 360 | fold_ar_c = as_c(fold_ar) 361 | count_ar = np.zeros(nbins*nints*nbands,dtype="int32") 362 | count_ar_c = as_c(count_ar) 363 | for nsamps,ii,data in self.readPlan(gulp,skipback=max_delay): 364 | self.lib.foldFil(as_c(data), 365 | fold_ar_c, 366 | count_ar_c, 367 | chan_delays_c, 368 | C.c_int(max_delay), 369 | C.c_double(self.header.tsamp), 370 | C.c_double(period), 371 | C.c_double(accel), 372 | C.c_int(self.header.nsamples), 373 | C.c_int(nsamps), 374 | C.c_int(self.header.nchans), 375 | C.c_int(nbins), 376 | C.c_int(nints), 377 | C.c_int(nbands), 378 | C.c_int(ii*(gulp-max_delay))) 379 | fold_ar/=count_ar 380 | fold_ar = fold_ar.reshape(nints,nbands,nbins) 381 | return FoldedData(fold_ar,self.header.newHeader(),period,dm,accel) 382 | 383 | 384 | def getChan(self,chan,gulp=512): 385 | """Retrieve a single frequency channel from the data. 386 | 387 | :param chan: channel to retrieve (0 is the highest frequency channel) 388 | :type chan: int 389 | 390 | :param gulp: number of samples in each read 391 | :type gulp: int 392 | 393 | :return: selected channel as a time series 394 | :rtype: :class:`~sigpyproc.TimeSeries.TimeSeries` 395 | """ 396 | if chan >= self.header.nchans or chan < 0: 397 | raise ValueError,"Selected channel out of range." 398 | tim_ar = np.empty(self.header.nsamples,dtype="float32") 399 | tim_ar_c = as_c(tim_ar) 400 | for nsamps,ii,data in self.readPlan(gulp): 401 | self.lib.getChan(as_c(data), 402 | tim_ar_c, 403 | C.c_int(chan), 404 | C.c_int(self.header.nchans), 405 | C.c_int(nsamps), 406 | C.c_int(ii*gulp)) 407 | return TimeSeries(tim_ar,self.header.newHeader({"channel":chan,"refdm":0.0,"nchans":1})) 408 | 409 | def split(self,start,nsamps,filename=None,gulp=1024,back_compatible=True): 410 | """Split data in time. 411 | 412 | :param start: start sample of split 413 | :type start: int 414 | 415 | :param nsamps: number of samples in split 416 | :type nsamps: int 417 | 418 | :param filename: name of output file 419 | :type filename: :func:`str` 420 | 421 | :param gulp: number of samples in each read 422 | :type gulp: int 423 | 424 | :param back_compatible: sigproc compatibility flag (legacy code) 425 | :type back_compatible: bool 426 | 427 | :return: name of new file 428 | :rtype: :func:`str` 429 | """ 430 | if filename is None: 431 | filename = "%s_%d_%d.fil"%(self.header.basename,start,start+nsamps) 432 | new_tstart = self.header.tstart + ((self.header.tsamp * start) / 86400.0) 433 | out_file = self.header.prepOutfile(filename, updates={'tstart': new_tstart}, nbits=self.header.nbits) 434 | for count, ii, data in self.readPlan(gulp,start=start,nsamps=nsamps): 435 | out_file.cwrite(data) 436 | out_file.close() 437 | return out_file.name 438 | 439 | def splitToChans(self,gulp=1024,back_compatible=True): 440 | """Split the data into component channels and write each to file. 441 | 442 | :param gulp: number of samples in each read 443 | :type gulp: int 444 | 445 | :param back_compatible: sigproc compatibility flag (legacy code) 446 | :type back_compatible: bool 447 | 448 | :return: names of all files written to disk 449 | :rtype: :func:`list` of :func:`str` 450 | 451 | .. note:: 452 | 453 | Time series are written to disk with names based on channel number. 454 | 455 | """ 456 | tim_ar = np.empty([self.header.nchans,gulp],dtype="float32") 457 | tim_ar_c = as_c(tim_ar) 458 | out_files = [self.header.prepOutfile("%s_chan%04d.tim"%(self.header.basename,ii), 459 | {"nchans":1,"nbits":32,"data_type":2}, 460 | back_compatible=back_compatible,nbits=32) 461 | 462 | for ii in xrange(self.header.nchans)] 463 | for nsamps,ii,data in self.readPlan(gulp): 464 | self.lib.splitToChans(as_c(data), 465 | tim_ar_c, 466 | C.c_int(self.header.nchans), 467 | C.c_int(nsamps), 468 | C.c_int(gulp)) 469 | for ii,f in enumerate(out_files): 470 | f.cwrite(tim_ar[ii][:nsamps]) 471 | 472 | for f in out_files: 473 | f.close() 474 | 475 | return [f.name for f in out_files] 476 | 477 | def getStats(self,gulp=512): 478 | """Retrieve channelwise statistics of data. 479 | 480 | :param gulp: number of samples in each read 481 | :type gulp: int 482 | 483 | Function creates four instance attributes: 484 | 485 | * :attr:`chan_means`: the mean value of each channel 486 | * :attr:`chan_stdevs`: the standard deviation of each channel 487 | * :attr:`chan_max`: the maximum value of each channel 488 | * :attr:`chan_min`: the minimum value of each channel 489 | """ 490 | maxima_ar = np.zeros(self.header.nchans,dtype="float32") 491 | minima_ar = np.zeros(self.header.nchans,dtype="float32") 492 | means_ar = np.zeros(self.header.nchans,dtype="float32") 493 | stdev_ar = np.zeros(self.header.nchans,dtype="float32") 494 | maxima_ar_c = as_c(maxima_ar) 495 | minima_ar_c = as_c(minima_ar) 496 | means_ar_c = as_c(means_ar) 497 | stdev_ar_c = as_c(stdev_ar) 498 | for nsamps,ii,data in self.readPlan(gulp): 499 | self.lib.getStats(as_c(data), 500 | means_ar_c, 501 | stdev_ar_c, 502 | maxima_ar_c, 503 | minima_ar_c, 504 | C.c_int(self.header.nchans), 505 | C.c_int(nsamps), 506 | C.c_int(ii)) 507 | 508 | means_ar /= self.header.nsamples 509 | stdev_ar = np.sqrt((stdev_ar/self.header.nsamples)-means_ar**2) 510 | stdev_ar[np.where(np.isnan(stdev_ar))] = 0 511 | self.chan_means = means_ar 512 | self.chan_stdevs = stdev_ar 513 | self.chan_maxima = maxima_ar 514 | self.chan_minima = minima_ar 515 | 516 | class FilterbankBlock(np.ndarray): 517 | """Class to handle a discrete block of data in time-major order. 518 | 519 | :param input_array: 2 dimensional array of shape (nchans,nsamples) 520 | :type input_array: :class:`numpy.ndarray` 521 | 522 | :param header: observational metadata 523 | :type header: :class:`~sigpyproc.Header.Header` 524 | 525 | .. note:: 526 | 527 | Data is converted to 32 bits regardless of original type. 528 | """ 529 | def __new__(cls,input_array,header): 530 | obj = input_array.astype("float32").view(cls) 531 | obj.header = header 532 | obj.lib = C.CDLL("libSigPyProc32.so") 533 | obj.dm = 0.0 534 | return obj 535 | 536 | def __array_finalize__(self,obj): 537 | if obj is None: return 538 | if hasattr(obj,"header"): 539 | self.header = obj.header 540 | self.lib = C.CDLL("libSigPyProc32.so") 541 | self.dm = getattr(obj,"dm",0.0) 542 | 543 | def downsample(self,tfactor=1,ffactor=1): 544 | """Downsample data block in frequency and/or time. 545 | 546 | :param tfactor: factor by which to downsample in time 547 | :type tfactor: int 548 | 549 | :param ffactor: factor by which to downsample in frequency 550 | :type ffactor: int 551 | 552 | :return: 2 dimensional array of downsampled data 553 | :rtype: :class:`~sigpyproc.Filterbank.FilterbankBlock` 554 | 555 | .. note:: 556 | 557 | ffactor must be a factor of nchans. 558 | 559 | """ 560 | if not self.shape[0]%ffactor == 0: 561 | raise ValueError,"Bad frequency factor given" 562 | newnsamps = self.shape[1] - self.shape[1]%tfactor 563 | new_ar = np.empty(newnsamps*self.shape[0]/ffactor/tfactor,dtype="float32") 564 | ar = self.transpose().ravel().copy() 565 | self.lib.downsample(as_c(ar), 566 | as_c(new_ar), 567 | C.c_int(tfactor), 568 | C.c_int(ffactor), 569 | C.c_int(self.shape[0]), 570 | C.c_int(newnsamps)) 571 | new_ar = new_ar.reshape(newnsamps//tfactor,self.shape[0]//ffactor).transpose() 572 | new_tsamp = self.header.tsamp*tfactor 573 | new_nchans = self.header.nchans//ffactor 574 | new_header = self.header.newHeader({"tsamp":new_tsamp,"nchans":new_nchans}) 575 | return FilterbankBlock(new_ar,new_header) 576 | 577 | def toFile(self,filename=None,back_compatible=True): 578 | """Write the data to file. 579 | 580 | :param filename: name of the output file (defaults to ``basename_split_start_to_end.fil``) 581 | :type filename: str 582 | 583 | :param back_compatible: sigproc compatibility flag (legacy code) 584 | :type back_compatible: bool 585 | 586 | :return: name of output file 587 | :rtype: :func:`str` 588 | """ 589 | if filename is None: 590 | filename = "%s_%d_to_%d.fil"%(self.header.basename,self.header.tstart, 591 | self.header.mjdAfterNsamps(self.shape[1])) 592 | new_header = {"nbits":32} 593 | out_file = self.header.prepOutfile(filename,new_header,nbits=32,back_compatible=back_compatible) 594 | out_file.cwrite(self.transpose().ravel()) 595 | return filename 596 | 597 | def normalise(self): 598 | """Divide each frequency channel by its average. 599 | 600 | :return: normalised version of the data 601 | :rtype: :class:`~sigpyproc.Filterbank.FilterbankBlock` 602 | """ 603 | return self/self.mean(axis=1).reshape(self.shape[0],1) 604 | 605 | def get_tim(self): 606 | return self.sum(axis=0) 607 | 608 | def get_bandpass(self): 609 | return self.sum(axis=1) 610 | 611 | def dedisperse(self,dm): 612 | """Dedisperse the block. 613 | 614 | :param dm: dm to dedisperse to 615 | :type dm: float 616 | 617 | :return: a dedispersed version of the block 618 | :rtype: :class:`~sigpyproc.Filterbank.FilterbankBlock` 619 | 620 | .. note:: 621 | 622 | Frequency dependent delays are applied as rotations to each 623 | channel in the block. 624 | """ 625 | new_ar = self.copy() 626 | delays = self.header.getDMdelays(dm) 627 | for ii in range(self.shape[0]): 628 | new_ar[ii] = rollArray(self[ii],delays[ii]%self.shape[1],0) 629 | new_ar.dm = dm 630 | return new_ar 631 | 632 | from sigpyproc.TimeSeries import TimeSeries 633 | 634 | --------------------------------------------------------------------------------