Skip to content

XDATData

Class for IQ Data Tektronix (TM) X-COM XDATData

xaratustrah@github Juli 2020

XDATData

Bases: IQBase

Source code in iqtools/xdatdata.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class XDATData(IQBase):
    def __init__(self, filename, header_filename):
        super().__init__(filename)

        # Additional fields in this subclass
        self.center = 0
        self.acq_bw = 0
        self.date_time = ''

        self.header_filename = header_filename
        self.read_header()

    def read(self, nframes=10, lframes=1024, sframes=0):
        """Read a section of the file.

        Args:
            nframes (int, optional): Number of frames to be read. Defaults to 10.
            lframes (int, optional): Length of each frame. Defaults to 1024.
            sframes (int, optional): Starting frame. Defaults to 0.
        """        

        self.read_samples(nframes * lframes, offset=sframes * lframes)

    def read_samples(self, nsamples, offset=0):
        """Read samples. The X-COM XDAT format can be described as a 16 integer interlaced
        I & Q file. I and Q are each a 16 bit little endian integer. I & Q are interlaced 
        together in a single file or memory buffer with order being I,Q.

        Args:
            nsamples (int): Number of samples to read from file
            offset (int, optional): _description_. Defaults to 0.

        """        
        filesize = os.path.getsize(self.filename)
        # each file contains 15625 blocks
        if not filesize == 4 * self.nsamples_total:
            raise ValueError(
                "File size does not match total number of samples. Aborting...")

        if nsamples > self.nsamples_total - offset:
            raise ValueError(
                'Requested number of samples is larger than the available {} samples.'.format(self.nsamples_total))

        total_n_bytes = 8 * nsamples  # 8 comes from 2 times 4 byte integer for I and Q
        start_n_bytes = 8 * offset

        try:
            with open(self.filename, 'rb') as f:
                f.seek(start_n_bytes)
                ba = f.read(total_n_bytes)
        except Exception as e:
            log.error(e + 'File seems to end here!')
            return

        # return a numpy array of little endian 8 byte floats (known as doubles)
        # little endian 4 byte ints.
        self.data_array = np.fromstring(ba, dtype='<i4')
        # Scale to retrieve value in Volts. Augmented assignment does not work here!
        self.data_array = self.data_array * self.scale
        self.data_array = self.data_array.view(
            dtype='c16')  # reinterpret the bytes as a 16 byte complex number, which consists of 2 doubles.

        log.info("Output complex array has a size of {}.".format(
            self.data_array.size))
        # in order to read you may use: data = x.item()['data'] or data = x[()]['data'] other wise you get 0-d error

    def read_header(self):
        """Parse XDAT header file
        """        
        tree = et.parse(self.header_filename)
        root = tree.getroot()
        for feature in root.iter('recording'):
            self.center = float(feature.get('center_frequency'))
            self.scale = float(feature.get('acq_scale_factor'))
            self.acq_bw = float(feature.get('acquisition_bandwidth'))
            self.fs = float(feature.get('sample_rate'))
            self.date_time = feature.get('creation_time')

        for feature in root.iter('data'):
            self.nsamples_total = int(feature.get('samples'))

read(nframes=10, lframes=1024, sframes=0)

Read a section of the file.

Parameters:

Name Type Description Default
nframes int

Number of frames to be read. Defaults to 10.

10
lframes int

Length of each frame. Defaults to 1024.

1024
sframes int

Starting frame. Defaults to 0.

0
Source code in iqtools/xdatdata.py
30
31
32
33
34
35
36
37
38
39
def read(self, nframes=10, lframes=1024, sframes=0):
    """Read a section of the file.

    Args:
        nframes (int, optional): Number of frames to be read. Defaults to 10.
        lframes (int, optional): Length of each frame. Defaults to 1024.
        sframes (int, optional): Starting frame. Defaults to 0.
    """        

    self.read_samples(nframes * lframes, offset=sframes * lframes)

read_header()

Parse XDAT header file

Source code in iqtools/xdatdata.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def read_header(self):
    """Parse XDAT header file
    """        
    tree = et.parse(self.header_filename)
    root = tree.getroot()
    for feature in root.iter('recording'):
        self.center = float(feature.get('center_frequency'))
        self.scale = float(feature.get('acq_scale_factor'))
        self.acq_bw = float(feature.get('acquisition_bandwidth'))
        self.fs = float(feature.get('sample_rate'))
        self.date_time = feature.get('creation_time')

    for feature in root.iter('data'):
        self.nsamples_total = int(feature.get('samples'))

read_samples(nsamples, offset=0)

Read samples. The X-COM XDAT format can be described as a 16 integer interlaced I & Q file. I and Q are each a 16 bit little endian integer. I & Q are interlaced together in a single file or memory buffer with order being I,Q.

Parameters:

Name Type Description Default
nsamples int

Number of samples to read from file

required
offset int

description. Defaults to 0.

0
Source code in iqtools/xdatdata.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def read_samples(self, nsamples, offset=0):
    """Read samples. The X-COM XDAT format can be described as a 16 integer interlaced
    I & Q file. I and Q are each a 16 bit little endian integer. I & Q are interlaced 
    together in a single file or memory buffer with order being I,Q.

    Args:
        nsamples (int): Number of samples to read from file
        offset (int, optional): _description_. Defaults to 0.

    """        
    filesize = os.path.getsize(self.filename)
    # each file contains 15625 blocks
    if not filesize == 4 * self.nsamples_total:
        raise ValueError(
            "File size does not match total number of samples. Aborting...")

    if nsamples > self.nsamples_total - offset:
        raise ValueError(
            'Requested number of samples is larger than the available {} samples.'.format(self.nsamples_total))

    total_n_bytes = 8 * nsamples  # 8 comes from 2 times 4 byte integer for I and Q
    start_n_bytes = 8 * offset

    try:
        with open(self.filename, 'rb') as f:
            f.seek(start_n_bytes)
            ba = f.read(total_n_bytes)
    except Exception as e:
        log.error(e + 'File seems to end here!')
        return

    # return a numpy array of little endian 8 byte floats (known as doubles)
    # little endian 4 byte ints.
    self.data_array = np.fromstring(ba, dtype='<i4')
    # Scale to retrieve value in Volts. Augmented assignment does not work here!
    self.data_array = self.data_array * self.scale
    self.data_array = self.data_array.view(
        dtype='c16')  # reinterpret the bytes as a 16 byte complex number, which consists of 2 doubles.

    log.info("Output complex array has a size of {}.".format(
        self.data_array.size))