Source code for directdemod.source

'''
A source is any input data source.
Say a IQ.wav file
'''

from abc import ABCMeta, abstractmethod
import directdemod.constants as constants
import numpy as np
import scipy.io.wavfile

'''
Abstract model of a class, to keep the models consistent
Any source must inherit this abstract class
The general structure is as follows
The goal of having such a parent abstract class is that, the children are forced to implement the required methods
'''

class source(metaclass=ABCMeta):
    ### Properties

    # A source type variable (different types defined in constant.py)
    @property
    @abstractmethod
    def sourceType(self):
        pass

    # The source sampling frequency
    @property
    @abstractmethod
    def sampFreq(self):
        pass

    # The source data length
    @property
    @abstractmethod
    def length(self):
        pass

    ### Methods

    # Every source must have a read method
    # Description: read values from 'fromIndex' to 'toIndex'
    # NecessaryInputs: fromIndex
    # OptionalInputs: toIndex
    @abstractmethod
    def read(self, fromIndex, toIndex):
        pass

'''
An IQ.wav file source, typically an output recorded from SDRSHARP
The IQ wav file contains two channels, one channel for I component and the other for Q
'''
[docs]class IQwav(source): ''' An IQ.wav file source, typically an output recorded from SDRSHARP or other similar software '''
[docs] def __init__(self, filename, givenSampFreq = None): '''Initialize the object Args: filename (:obj:`str`): filename of the IQ.wav file ''' self.__offset = 0 self.memmap = np.memmap(filename, offset=44) self.__sourceType = constants.SOURCE_IQWAV self.__sampFreq, self.__data = scipy.io.wavfile.read(filename, True) if not givenSampFreq is None: self.__sampFreq = givenSampFreq self.__actualLength = self.__data.shape[0] self.__length = self.__data.shape[0]
@property def sampFreq(self): ''':obj:`int`: get sampling freq of source''' return self.__sampFreq @property def sourceType(self): ''':obj:`int`: get source type''' return self.__sourceType @property def length(self): ''':obj:`int`: get source length''' return self.__length
[docs] def read(self, fromIndex, toIndex = None): '''Read source data Args: fromIndex (:obj:`int`): starting index toIndex (:obj:`int`, optional): ending index. If not provided, the element at location given by fromIndex is returned Returns: :obj:`numpy array`: Complex IQ numbers in an array ''' fromIndex += self.__offset if toIndex == None: toIndex = fromIndex + 1 else: toIndex += self.__offset if fromIndex-self.__offset < 0 or toIndex-self.__offset < 0 or fromIndex-self.__offset >= self.length or toIndex-self.__offset > self.length: raise ValueError("fromIndex and toIndex have invalid values") samples = self.__data[fromIndex:toIndex,0] + 1j * self.__data[fromIndex:toIndex,1] return np.array(samples).astype("complex64") - (127.5 + 1j*127.5)
[docs] def limitData(self, initOffset = None, finalLimit = None): '''Limit source data Args: initOffset (:obj:`int`, optional): starting index finalLimit (:obj:`int`, optional): ending index ''' if not initOffset is None: self.__offset = initOffset else: self.__offset = 0 if not finalLimit is None: self.__length = finalLimit - self.__offset else: self.__length = self.__actualLength
''' An IQ.dat file source The IQ dat file contains two channels, one channel for I component and the other for Q ''' class IQdat(source): ''' An IQ.dat file source ''' def __init__(self, filename, givenSampFreq = None): '''Initialize the object Args: filename (:obj:`str`): filename of the IQ.dat file ''' self.__offset = 0 self.__sourceType = constants.SOURCE_IQDAT self.memmap = np.memmap(filename) self.__data = np.memmap(filename) self.__length = int(len(self.__data)/2) self.__sampFreq = constants.IQ_SDRSAMPRATE if not givenSampFreq is None: self.__sampFreq = givenSampFreq self.__actualLength = int(len(self.__data)/2) @property def sampFreq(self): ''':obj:`int`: get sampling freq of source''' return self.__sampFreq @property def sourceType(self): ''':obj:`int`: get source type''' return self.__sourceType @property def length(self): ''':obj:`int`: get source length''' return self.__length def read(self, fromIndex, toIndex = None): '''Read source data Args: fromIndex (:obj:`int`): starting index toIndex (:obj:`int`, optional): ending index. If not provided, the element at location given by fromIndex is returned Returns: :obj:`numpy array`: Complex IQ numbers in an array ''' fromIndex += self.__offset if toIndex == None: toIndex = fromIndex + 1 else: toIndex += self.__offset if fromIndex-self.__offset < 0 or toIndex-self.__offset < 0 or fromIndex-self.__offset >= self.length or toIndex-self.__offset > self.length: raise ValueError("fromIndex and toIndex have invalid values") samples = (self.__data[2*fromIndex:2*toIndex:2]) + 1j * (self.__data[1+2*fromIndex:1+2*toIndex:2]) return np.array(samples).astype("complex64") - (127.5 + 1j*127.5) def limitData(self, initOffset = None, finalLimit = None): '''Limit source data Args: initOffset (:obj:`int`, optional): starting index finalLimit (:obj:`int`, optional): ending index ''' if not initOffset is None: self.__offset = initOffset else: self.__offset = 0 if not finalLimit is None: self.__length = finalLimit - self.__offset else: self.__length = self.__actualLength ''' Note: This is an alternative implementation, directly using np.memmap An IQ.wav file source, typically an output recorded from SDRSHARP The IQ wav file contains two channels, one channel for I component and the other for Q ''' class IQwavAlt(source): ''' Note: This is an alternative implementation, directly using np.memmap An IQ.wav file source, typically an output recorded from SDRSHARP ''' def __init__(self, filename, givenSampFreq = None): '''Initialize the object Args: filename (:obj:`str`): filename of the IQ.wav file ''' self.__offset = 0 self.__sourceType = constants.SOURCE_IQWAV self.__data = np.memmap(filename, offset=44) self.memmap = np.memmap(filename, offset=44) self.__length = int(len(self.__data)/2) self.__sampFreq = constants.IQ_SDRSAMPRATE if not givenSampFreq is None: self.__sampFreq = givenSampFreq self.__actualLength = int(len(self.__data)/2) @property def sampFreq(self): ''':obj:`int`: get sampling freq of source''' return self.__sampFreq @property def sourceType(self): ''':obj:`int`: get source type''' return self.__sourceType @property def length(self): ''':obj:`int`: get source length''' return self.__length def read(self, fromIndex, toIndex = None): '''Read source data Args: fromIndex (:obj:`int`): starting index toIndex (:obj:`int`, optional): ending index. If not provided, the element at location given by fromIndex is returned Returns: :obj:`numpy array`: Complex IQ numbers in an array ''' fromIndex += self.__offset if toIndex == None: toIndex = fromIndex + 1 else: toIndex += self.__offset if fromIndex-self.__offset < 0 or toIndex-self.__offset < 0 or fromIndex-self.__offset >= self.length or toIndex-self.__offset > self.length: raise ValueError("fromIndex and toIndex have invalid values") samples = (self.__data[2*fromIndex:2*toIndex:2]) + 1j * (self.__data[1+2*fromIndex:1+2*toIndex:2]) return np.array(samples).astype("complex64") - (127.5 + 1j*127.5) def limitData(self, initOffset = None, finalLimit = None): '''Limit source data Args: initOffset (:obj:`int`, optional): starting index finalLimit (:obj:`int`, optional): ending index ''' if not initOffset is None: self.__offset = initOffset else: self.__offset = 0 if not finalLimit is None: self.__length = finalLimit - self.__offset else: self.__length = self.__actualLength