Beispiel #1
0
    def setModelData(self, editor, model, index):
        """
        When editor has finished, read the updated data from the editor,
        convert it back to floating point format and store it in both the model
        (= QTableWidget) and in self.ba. Finally, refresh the table item to 
        display it in the selected format (via `float2frmt()`).

        editor: instance of e.g. QLineEdit
        model:  instance of QAbstractTableModel
        index:  instance of QModelIndex
        """

        # check for different editor environments if needed and provide a default:
#        if isinstance(editor, QtGui.QTextEdit):
#            model.setData(index, editor.toPlainText())
#        elif isinstance(editor, QComboBox):
#            model.setData(index, editor.currentText())
#        else:
#            super(ItemDelegate, self).setModelData(editor, model, index)
        if self.parent.myQ.frmt == 'float':
            data = safe_eval(qstr(editor.text()), 
                             self.parent.ba[index.column()][index.row()], return_type='auto') # raw data without fixpoint formatting
        else:
            data = self.parent.myQ.frmt2float(qstr(editor.text()),
                                    self.parent.myQ.frmt) # transform back to float

        model.setData(index, data)                          # store in QTableWidget
        self.parent.ba[index.column()][index.row()] = data  # and in self.ba
        qstyle_widget(self.parent.ui.butSave, 'changed')
        self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
Beispiel #2
0
    def setModelData(self, editor, model, index):
        """
        When editor has finished, read the updated data from the editor,
        convert it back to floating point format and store it in the model 
        (= QTableWidget) and in self.ba

        editor: instance of e.g. QLineEdit
        model:  instance of QAbstractTableModel
        index:  instance of QModelIndex
        """
        # check for different editor environments if needed and provide a default:
        #        if isinstance(editor, QtGui.QTextEdit):
        #            model.setData(index, editor.toPlainText())
        #        elif isinstance(editor, QComboBox):
        #            model.setData(index, editor.currentText())
        #        else:
        #            super(ItemDelegate, self).setModelData(editor, model, index)
        if self.parent.myQ.frmt == 'float':
            data = safe_eval(
                qstr(editor.text()),
                fb.data_old)  # raw data without fixpoint formatting
        else:
            data = self.parent.myQ.frmt2float(
                qstr(editor.text()),
                self.parent.myQ.frmt)  # transform back to float

        model.setData(index, data)  # store in QTableWidget
        self.parent.ba[index.column()][index.row()] = data  # and in self.ba
        qstyle_widget(self.parent.butSave, 'changed')
Beispiel #3
0
    def setModelData(self, editor, model, index):
        """
        When editor has finished, read the updated data from the editor,
        convert it back to floating point format and store it in both the model
        (= QTableWidget) and in self.ba. Finally, refresh the table item to 
        display it in the selected format (via `float2frmt()`).

        editor: instance of e.g. QLineEdit
        model:  instance of QAbstractTableModel
        index:  instance of QModelIndex
        """

        # check for different editor environments if needed and provide a default:
#        if isinstance(editor, QtGui.QTextEdit):
#            model.setData(index, editor.toPlainText())
#        elif isinstance(editor, QComboBox):
#            model.setData(index, editor.currentText())
#        else:
#            super(ItemDelegate, self).setModelData(editor, model, index)
        if self.parent.myQ.frmt == 'float':
            data = safe_eval(qstr(editor.text()), 
                             self.parent.ba[index.column()][index.row()], return_type='auto') # raw data without fixpoint formatting
        else:
            data = self.parent.myQ.frmt2float(qstr(editor.text()),
                                    self.parent.myQ.frmt) # transform back to float

        model.setData(index, data)                          # store in QTableWidget
        # if the entry is complex, convert ba (list of arrays) to complex type
        if isinstance(data, complex):
            self.parent.ba[0] = self.parent.ba[0].astype(complex)
            self.parent.ba[1] = self.parent.ba[1].astype(complex)
        self.parent.ba[index.column()][index.row()] = data  # store in self.ba
        qstyle_widget(self.parent.ui.butSave, 'changed')
        self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
    def _construct_UI(self, **kwargs):
        """ Construct widget """

        dict_ui = {'label':'WI.WF', 'max_led_width':30,
                   'WI':0, 'WI_len':2, 'tip_WI':'Number of integer bits',
                   'WF':15,'WF_len':2, 'tip_WF':'Number of fractional bits',
                   'enabled':True, 'visible':True
                   }
        for key, val in kwargs.items():
            dict_ui.update({key:val})
        # dict_ui.update(map(kwargs)) # same as above?
        self.WI = dict_ui['WI']
        self.WF = dict_ui['WF']

        lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self)
        self.ledWI = QLineEdit(self)
        self.ledWI.setToolTip(dict_ui['tip_WI'])
        self.ledWI.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits
        self.ledWI.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?)

        lblDot = QLabel(".", self)

        self.ledWF = QLineEdit(self)
        self.ledWF.setToolTip(dict_ui['tip_WF'])
        self.ledWF.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits
        self.ledWF.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?)

        layH = QHBoxLayout()
        layH.addWidget(lblW)
        layH.addStretch()
        layH.addWidget(self.ledWI)
        layH.addWidget(lblDot)
        layH.addWidget(self.ledWF)
        layH.setContentsMargins(0,0,0,0)

        frmMain = QFrame(self)
        frmMain.setLayout(layH)

        layVMain = QVBoxLayout() # Widget main layout
        layVMain.addWidget(frmMain)
        layVMain.setContentsMargins(0,5,0,0)#*params['wdg_margins'])

        self.setLayout(layVMain)
        
        #----------------------------------------------------------------------
        # INITIAL SETTINGS
        #----------------------------------------------------------------------
        self.ledWI.setText(qstr(dict_ui['WI']))
        self.ledWF.setText(qstr(dict_ui['WF']))

        frmMain.setEnabled(dict_ui['enabled'])
        frmMain.setVisible(dict_ui['visible'])

        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.ledWI.editingFinished.connect(self.save_ui)
        self.ledWF.editingFinished.connect(self.save_ui)
 def save_ui(self):
     """ 
     Update the attributes `self.WI` and `self.WF` when one of the QLineEdit
     widgets has been edited.
     """
     self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos')
     self.ledWI.setText(qstr(self.WI))
     self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos')
     self.ledWF.setText(qstr(self.WF))
Beispiel #6
0
    def setupHDL(self, file_name="", dir_name=""):
        """
        Setup instance of myHDL object with word lengths and coefficients
        """
        self.qI_i = safe_eval(self.ledWIInput.text(),
                              return_type='int',
                              sign='pos')
        self.qF_i = safe_eval(self.ledWFInput.text(),
                              return_type='int',
                              sign='pos')
        self.ledWIInput.setText(qstr(self.qI_i))
        self.ledWFInput.setText(qstr(self.qF_i))

        self.qI_o = safe_eval(self.ledWIOutput.text(),
                              return_type='int',
                              sign='pos')
        self.qF_o = safe_eval(self.ledWFOutput.text(),
                              return_type='int',
                              sign='pos')
        self.ledWIOutput.setText(qstr(self.qI_o))
        self.ledWFOutput.setText(qstr(self.qF_o))

        qQuant_o = self.cmbQuant_o.currentText()
        qOvfl_o = self.cmbOvfl_o.currentText()

        q_obj_o = {
            'WI': self.qI_o,
            'WF': self.qF_o,
            'quant': qQuant_o,
            'ovfl': qOvfl_o
        }
        myQ_o = fix.Fixed(q_obj_o)  # instantiate fixed-point object

        self.W = (self.qI_i + self.qF_i + 1, self.qF_i
                  )  # Matlab format: (W,WF)

        # @todo: always use sos?  The filter object is setup to always
        # @todo: generate a second order filter
        # get filter coefficients etc. from filter dict
        coeffs = fb.fil[0]['ba']
        zpk = fb.fil[0]['zpk']
        sos = fb.fil[0]['sos']

        logger.info("W = {0}".format(self.W))
        logger.info('b = {0}'.format(coeffs[0][0:3]))
        logger.info('a = {0}'.format(coeffs[1][0:3]))

        # =============== adapted from C. Felton's SIIR example =============
        self.flt = FilterIIR(
            b=np.array(coeffs[0][0:3]),
            a=np.array(coeffs[1][0:3]),
            #sos = sos, doesn't work yet
            word_format=(self.W[0], 0, self.W[1]))

        self.flt.hdl_name = file_name
        self.flt.hdl_directory = dir_name
 def save_ui(self):
     """ 
     Update the attributes `self.WI` and `self.WF` and the filter dict 
     when one of the QLineEdit widgets has been edited.
     """
     self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos')
     self.ledWI.setText(qstr(self.WI))
     self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos')
     self.ledWF.setText(qstr(self.WF))
     fb.fil[0]["q_coeff"].update({'WI':self.WI, 'WF':self.WF})
    def simFixPoint(self):
        """
        Simulate filter in fix-point description
        """
        # Setup the Testbench and run

        dlg = QFD(self)  # instantiate file dialog object

        plt_types = "png (*.png);;svg (*.svg)"

        plt_dir_file, plt_type = dlg.getSaveFileName_(caption="Save plots as",
                                                      directory=dirs.save_dir,
                                                      filter=plt_types)
        plt_dir_file = qstr(plt_dir_file)  # dir + file name without suffix

        if plt_dir_file != "":
            plt_type = extract_file_ext(qstr(plt_type))  # suffix
            plt_dir_file += plt_type[0]  # file name with suffix
            plt_dir_file = os.path.normpath(plt_dir_file)  # "sanitize" path

            plt_dir = os.path.dirname(
                plt_dir_file)  # extract the directory path
            if not os.path.isdir(
                    plt_dir):  # create directory if it doesn't exist
                os.mkdir(plt_dir)
            dirs.save_dir = plt_dir  # make this directory the new default / base dir

            plt_file = os.path.basename(plt_dir_file)

            logger.info('Creating plot file "{0}"'.format(
                os.path.join(plt_dir, plt_file)))

            self.setupHDL(file_name=plt_file, dir_name=plt_dir)

            logger.info("Fixpoint simulation setup")
            W = self.hdl_wdg_inst.W  # Matlab format : W = (W_len,WF)
            tb = self.hdl_wdg_inst.flt.simulate_freqz(num_loops=3, Nfft=1024)
            clk = myhdl.Signal(False)
            ts = myhdl.Signal(False)
            x = myhdl.Signal(
                myhdl.intbv(0, min=-2**(W[0] - 1), max=2**(W[0] - 1)))
            y = myhdl.Signal(
                myhdl.intbv(0, min=-2**(W[0] - 1), max=2**(W[0] - 1)))

            try:
                sim = myhdl.Simulation(tb)
                logger.info("Fixpoint simulation started")
                sim.run()
                logger.info("Fixpoint plotting started")
                self.hdl_wdg_inst.flt.plot_response()
                logger.info("Fixpoint plotting finished")
            except myhdl.SimulationError as e:
                logger.warning("Simulation failed:\n{0}".format(e))
    def load_ui(self):
        """ 
        Update the ui and the attributes `self.WI` and `self.WF` from the filter
        dict. `load_ui()` has to be called when the coefficients or the word
        format has been changed outside the class, e.g. by a new filter design or
        by changing the coefficient format in `input_coeffs.py`.
        """
        self.WI = fb.fil[0]['q_coeff']['WI']
        self.WF = fb.fil[0]['q_coeff']['WF']
        self.ledWI.setText(qstr(self.WI))
        self.ledWF.setText(qstr(self.WF))

        self.c_dict = build_coeff_dict()
Beispiel #10
0
 def _store_q_settings(self):
     """
     Read out the settings of the quantization comboboxes and store them in
     the filter dict. Update the fixpoint object.
     """
     fb.fil[0]['q_coeff'] = {
             'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'),
             'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='pos'),
             'quant':qstr(self.ui.cmbQuant.currentText()),
             'ovfl':qstr(self.ui.cmbQOvfl.currentText()),
             'frmt':qstr(self.ui.cmbFormat.currentText()),
             'scale':qstr(self.ui.ledScale.text())
             }
     self._load_q_settings() # update widgets and the fixpoint object self.myQ
Beispiel #11
0
 def _store_q_settings(self):
     """
     Read out the settings of the quantization comboboxes and store them in
     the filter dict. Update the fixpoint object.
     """
     fb.fil[0]['q_coeff'] = {
             'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'),
             'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='pos'),
             'quant':qstr(self.ui.cmbQuant.currentText()),
             'ovfl':qstr(self.ui.cmbQOvfl.currentText()),
             'frmt':qstr(self.ui.cmbFormat.currentText()),
             'scale':qstr(self.ui.ledScale.text())
             }
     self._load_q_settings() # update widgets and the fixpoint object self.myQ
Beispiel #12
0
    def setModelData(self, editor, model, index):
        """
        When editor has finished, read the updated data from the editor,
        convert it to floating point format and store it in both the model
        (= QTableWidget) and in `zpk`. Finally, refresh the table item to
        display it in the selected format (via `to be defined`) and normalize
        the gain.

        editor: instance of e.g. QLineEdit
        model:  instance of QAbstractTableModel
        index:  instance of QModelIndex
        """

        # check for different editor environments if needed and provide a default:
#        if isinstance(editor, QtGui.QTextEdit):
#            model.setData(index, editor.toPlainText())
#        elif isinstance(editor, QComboBox):
#            model.setData(index, editor.currentText())
#        else:
#            super(ItemDelegate, self).setModelData(editor, model, index)

        # convert entered string to complex, pass the old value as default
        data = self.parent.frmt2cmplx(qstr(editor.text()), 
                                      self.parent.zpk[index.column()][index.row()])
        model.setData(index, data)                          # store in QTableWidget
        self.parent.zpk[index.column()][index.row()] = data  # and in self.ba
        qstyle_widget(self.parent.ui.butSave, 'changed')
        self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
        self.parent._normalize_gain() # recalculate gain
Beispiel #13
0
    def setModelData(self, editor, model, index):
        """
        When editor has finished, read the updated data from the editor,
        convert it to complex format and store it in both the model
        (= QTableWidget) and in `zpk`. Finally, refresh the table item to
        display it in the selected format (via `to be defined`) and normalize
        the gain.

        editor: instance of e.g. QLineEdit
        model:  instance of QAbstractTableModel
        index:  instance of QModelIndex
        """

        # check for different editor environments if needed and provide a default:
        #        if isinstance(editor, QtGui.QTextEdit):
        #            model.setData(index, editor.toPlainText())
        #        elif isinstance(editor, QComboBox):
        #            model.setData(index, editor.currentText())
        #        else:
        #            super(ItemDelegate, self).setModelData(editor, model, index)

        # convert entered string to complex, pass the old value as default
        data = self.parent.frmt2cmplx(
            qstr(editor.text()), self.parent.zpk[index.column()][index.row()])
        model.setData(index, data)  # store in QTableWidget
        self.parent.zpk[index.column()][index.row()] = data  # and in self.ba
        qstyle_widget(self.parent.ui.butSave, 'changed')
        self.parent._refresh_table_item(index.row(),
                                        index.column())  # refresh table entry
        self.parent._normalize_gain()  # recalculate gain
    def _store_q_settings(self):
        """
        Read out the settings of the quantization comboboxes and store them in
        the filter dict. Update the fixpoint object and refresh table
        """
        fb.fil[0]['q_coeff'] = {
                'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'),
                'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='pos'),
                'quant':qstr(self.ui.cmbQuant.currentText()),
                'ovfl':qstr(self.ui.cmbQOvfl.currentText()),
                'frmt':qstr(self.ui.cmbFormat.currentText()),
                'scale':qstr(self.ui.ledScale.text())
                }
        self.sig_tx.emit({'sender':__name__, 'view_changed':'q_coeff'})

        self._load_q_settings() # update widgets and the fixpoint object self.myQ
        self._refresh_table()
    def _load_q_settings(self):
        """
        load the quantization settings from the filter dict and set the widgets
        accordingly. Update the fixpoint object.
        """
        self.myQ.setQobj(fb.fil[0]['q_coeff'])
        q_coeff = self.myQ.q_obj

        self.ui.ledWI.setText(qstr(q_coeff['WI']))
        self.ui.ledWF.setText(qstr(q_coeff['WF']))
        qset_cmb_box(self.ui.cmbQuant, q_coeff['quant'])
        qset_cmb_box(self.ui.cmbQOvfl,  q_coeff['ovfl'])
        qset_cmb_box(self.ui.cmbFormat, q_coeff['frmt'])
        self.ui.ledScale.setText(qstr(q_coeff['scale']))

        self.ui.ledW.setText(qstr(self.myQ.W))
        self.ui.lblLSB.setText("{0:.{1}g}".format(self.myQ.LSB, params['FMT_ba']))
        self.ui.lblMSB.setText("{0:.{1}g}".format(self.myQ.MSB, params['FMT_ba']))
        self.ui.lblMAX.setText("{0}".format(self.myQ.float2frmt(self.myQ.MAX/self.myQ.scale)))
    def exportHDL(self):
        """
        Synthesize HDL description of filter using myHDL module
        """
        dlg = QFD(self)  # instantiate file dialog object

        file_types = "Verilog (*.v);;VHDL (*.vhd)"

        hdl_file, hdl_filter = dlg.getSaveFileName_(caption="Save HDL as",
                                                    directory=dirs.save_dir,
                                                    filter=file_types)
        hdl_file = qstr(hdl_file)

        if hdl_file != "":  # "operation cancelled" gives back an empty string
            hdl_file = os.path.normpath(hdl_file)
            hdl_type = extract_file_ext(
                qstr(hdl_filter))[0]  # return '.v' or '.vhd'

            hdl_dir_name = os.path.dirname(
                hdl_file)  # extract the directory path
            if not os.path.isdir(
                    hdl_dir_name):  # create directory if it doesn't exist
                os.mkdir(hdl_dir_name)
            dirs.save_dir = hdl_dir_name  # make this directory the new default / base dir

            # return the filename without suffix
            hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0]

            self.setupHDL(file_name=hdl_file_name, dir_name=hdl_dir_name)

            if str(hdl_type) == '.vhd':
                self.hdl_wdg_inst.flt.hdl_target = 'vhdl'
                suffix = '.vhd'
            else:
                self.hdl_wdg_inst.flt.hdl_target = 'verilog'
                suffix = '.v'

            logger.info('Creating hdl_file "{0}"'.format(
                os.path.join(hdl_dir_name, hdl_file_name + suffix)))

            self.hdl_wdg_inst.flt.convert()
            logger.info("HDL conversion finished!")
Beispiel #17
0
    def cmplx2frmt(self, text, places=-1):
        """
        Convert number "text" (real or complex or string) to the format defined 
        by cmbPZFrmt.
        
        Returns: 
            string
        """
        # convert to "normal" string and prettify via safe_eval:
        data = safe_eval(qstr(text), return_type='auto')
        frmt = qget_cmb_box(self.ui.cmbPZFrmt) # get selected format

        if places == -1:
            full_prec = True
        else:
            full_prec = False

        if frmt == 'cartesian' or not (type(data) == complex):
            if full_prec:
                return "{0}".format(data)
            else:
                return "{0:.{plcs}g}".format(data, plcs=places)

        elif frmt == 'polar_rad':
            r, phi = np.absolute(data), np.angle(data, deg=False)
            if full_prec:
                return "{r} * {angle_char}{p} rad"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} rad"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        elif frmt == 'polar_deg':
            r, phi = np.absolute(data), np.angle(data, deg=True)
            if full_prec:
                return "{r} * {angle_char}{p}°"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g}°"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        elif frmt == 'polar_pi':
            r, phi = np.absolute(data), np.angle(data, deg=False) / np.pi
            if full_prec:
                return "{r} * {angle_char}{p} pi"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} pi"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        else:
            logger.error("Unknown format {0}.".format(frmt))
Beispiel #18
0
    def cmplx2frmt(self, text, places=-1):
        """
        Convert number "text" (real or complex or string) to the format defined 
        by cmbPZFrmt.
        
        Returns: 
            string
        """
        # convert to "normal" string and prettify via safe_eval:
        data = safe_eval(qstr(text), return_type='auto')
        frmt = qget_cmb_box(self.ui.cmbPZFrmt)  # get selected format

        if places == -1:
            full_prec = True
        else:
            full_prec = False

        if frmt == 'cartesian' or not (type(data) == complex):
            if full_prec:
                return "{0}".format(data)
            else:
                return "{0:.{plcs}g}".format(data, plcs=places)

        elif frmt == 'polar_rad':
            r, phi = np.absolute(data), np.angle(data, deg=False)
            if full_prec:
                return "{r} * {angle_char}{p} rad"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} rad"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        elif frmt == 'polar_deg':
            r, phi = np.absolute(data), np.angle(data, deg=True)
            if full_prec:
                return "{r} * {angle_char}{p}°"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g}°"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        elif frmt == 'polar_pi':
            r, phi = np.absolute(data), np.angle(data, deg=False) / np.pi
            if full_prec:
                return "{r} * {angle_char}{p} pi"\
                    .format(r=r, p=phi, angle_char=self.angle_char)
            else:
                return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} pi"\
                    .format(r=r, p=phi, plcs=places, angle_char=self.angle_char)

        else:
            logger.error("Unknown format {0}.".format(frmt))
Beispiel #19
0
    def displayText(self, text, locale):
        """
        Display `text` with selected fixpoint base and number of places

        text:   string / QVariant from QTableWidget to be rendered
        locale: locale for the text
        """
        string = qstr(text)  # convert to "normal" string

        if self.parent.myQ.frmt == 'float':
            data = safe_eval(string)
            return "{0:.{1}g}".format(data, params['FMT_ba'])
        else:
            return "{0:>{1}}".format(self.parent.myQ.float2frmt(string),
                                     self.parent.myQ.places)
Beispiel #20
0
    def frmt2cmplx(self, text, default=0.):
        """
        Convert format defined by cmbPZFrmt to real or complex
        """
        conv_error = False
        text = qstr(text).replace(
            " ", "")  # convert to "proper" string without blanks
        if qget_cmb_box(self.ui.cmbPZFrmt) == 'cartesian':
            return safe_eval(text, default, return_type='auto')
        else:
            polar_str = text.split('*' + self.angle_char, 1)
            if len(polar_str) < 2:  # input is real or imaginary
                r = safe_eval(re.sub('[' + self.angle_char + '<∠°]', '', text),
                              default,
                              return_type='auto')
                x = r.real
                y = r.imag
            else:
                r = safe_eval(polar_str[0], sign='pos')
                if safe_eval.err > 0:
                    conv_error = True

                if "°" in polar_str[1]:
                    scale = np.pi / 180.  # angle in degrees
                elif re.search('π$|pi$', polar_str[1]):
                    scale = np.pi
                else:
                    scale = 1.  # angle in rad

                # remove right-most special characters (regex $)
                polar_str[1] = re.sub(
                    '[' + self.angle_char + '<∠°π]$|rad$|pi$', '',
                    polar_str[1])
                phi = safe_eval(polar_str[1]) * scale
                if safe_eval.err > 0:
                    conv_error = True

                if not conv_error:
                    x = r * np.cos(phi)
                    y = r * np.sin(phi)
                else:
                    x = default.real
                    y = default.imag
                    logger.error(
                        "Expression {0} could not be evaluated.".format(text))
            return x + 1j * y
Beispiel #21
0
    def displayText(self, text, locale):
        """
        Display `text` with selected fixpoint base and number of places

        text:   string / QVariant from QTableWidget to be rendered
        locale: locale for the text

        The instance parameter myQ.ovr_flag is set to +1 or -1 for positive /
        negative overflows, else it is 0.
        """
        data_str = qstr(text)  # convert to "normal" string

        if self.parent.myQ.frmt == 'float':
            data = safe_eval(data_str, return_type='auto')  # convert to float
            return "{0:.{1}g}".format(data, params['FMT_ba'])
        else:
            return "{0:>{1}}".format(self.parent.myQ.float2frmt(data_str),
                                     self.parent.myQ.places)
Beispiel #22
0
    def displayText(self, text, locale):
        """
        Display `text` with selected fixpoint base and number of places

        text:   string / QVariant from QTableWidget to be rendered
        locale: locale for the text

        The instance parameter myQ.ovr_flag is set to +1 or -1 for positive /
        negative overflows, else it is 0.
        """
        data_str = qstr(text) # convert to "normal" string

        if self.parent.myQ.frmt == 'float':
            data = safe_eval(data_str, return_type='auto') # convert to float
            return "{0:.{1}g}".format(data, params['FMT_ba'])
        else:
            return "{0:>{1}}".format(self.parent.myQ.float2frmt(data_str),
                                        self.parent.myQ.places)
Beispiel #23
0
    def setEditorData(self, editor, index):
        """
        Pass the data to be edited to the editor:
        - retrieve data with full accuracy from self.ba (in float format)
        - requantize data according to settings in fixpoint object
        - represent it in the selected format (int, hex, ...)

        editor: instance of e.g. QLineEdit
        index:  instance of QModelIndex
        """
#        data = qstr(index.data()) # get data from QTableWidget
        data_str = qstr(safe_eval(self.parent.ba[index.column()][index.row()], return_type="auto"))

        if self.parent.myQ.frmt == 'float':
            # floating point format: pass data with full resolution
            editor.setText(data_str)
        else:
            # fixpoint format with base: pass requantized data with required number of places
            editor.setText("{0:>{1}}".format(self.parent.myQ.float2frmt(data_str),
                                               self.parent.myQ.places))
Beispiel #24
0
    def setEditorData(self, editor, index):
        """
        Pass the data to be edited to the editor:
        - retrieve data with full accuracy from self.ba (in float format)
        - requantize data according to settings in fixpoint object
        - represent it in the selected format (int, hex, ...)

        editor: instance of e.g. QLineEdit
        index:  instance of QModelIndex
        """
#        data = qstr(index.data()) # get data from QTableWidget
        data_str = qstr(safe_eval(self.parent.ba[index.column()][index.row()], return_type="auto"))

        if self.parent.myQ.frmt == 'float':
            # floating point format: pass data with full resolution
            editor.setText(data_str)
        else:
            # fixpoint format with base: pass requantized data with required number of places
            editor.setText("{0:>{1}}".format(self.parent.myQ.float2frmt(data_str),
                                               self.parent.myQ.places))
Beispiel #25
0
    def frmt2cmplx(self, text, default=0.):
        """
        Convert format defined by cmbPZFrmt to real or complex
        """
        conv_error = False
        text = qstr(text).replace(" ", "") # convert to "proper" string without blanks
        if qget_cmb_box(self.ui.cmbPZFrmt) == 'cartesian':
            return safe_eval(text, default, return_type='auto')
        else:
            polar_str = text.split('*' + self.angle_char, 1)
            if len(polar_str) < 2: # input is real or imaginary
                r = safe_eval(re.sub('['+self.angle_char+'<∠°]','', text), default, return_type='auto')
                x = r.real
                y = r.imag
            else:
                r = safe_eval(polar_str[0], sign='pos')
                if safe_eval.err > 0:
                    conv_error = True

                if "°" in polar_str[1]:
                    scale = np.pi / 180. # angle in degrees
                elif re.search('π$|pi$', polar_str[1]):
                    scale = np.pi
                else:
                    scale = 1. # angle in rad

                # remove right-most special characters (regex $)
                polar_str[1] = re.sub('['+self.angle_char+'<∠°π]$|rad$|pi$', '', polar_str[1])
                phi = safe_eval(polar_str[1]) * scale
                if safe_eval.err > 0:
                    conv_error = True

                if not conv_error:
                    x = r * np.cos(phi)
                    y = r * np.sin(phi)
                else:
                    x = default.real
                    y = default.imag
                    logger.error("Expression {0} could not be evaluated.".format(text))
            return x + 1j * y
Beispiel #26
0
 def text(self, item):
     """
     Return item text as string transformed by self.displayText()
     """
     # return qstr(item.text()) # convert to "normal" string
     return  qstr(self.displayText(item.text(), QtCore.QLocale()))
Beispiel #27
0
 def text(self, item):
     """
     Return item text as string transformed by self.displayText()
     """
     # return qstr(item.text()) # convert to "normal" string
     return qstr(self.displayText(item.text(), QtCore.QLocale()))
Beispiel #28
0
 def displayText(self, text, locale):
     return "{:.{n_digits}g}".format(safe_eval(qstr(text),
                                               return_type='cmplx'),
                                     n_digits=params['FMT_pz'])
Beispiel #29
0
    def frmt2float(self, y, frmt=None):
        """
        Return floating point representation for fixpoint scalar `y` given in
        format `frmt`.

        - Construct string representation without radix point, count number of
          fractional places.
        - Calculate integer representation of string, taking the base into account
        (- When result is negative, calculate two's complement for `W` bits)
        - Scale with `2** -W`
        - Scale with the number of fractional places (depending on format!)

        Parameters
        ----------
        y: scalar or string
            to be quantized with the numeric base specified by `frmt`.

        frmt: string (optional)
            any of the formats `float`, `dec`, `bin`, `hex`, `csd`)
            When `frmt` is unspecified, the instance parameter `self.frmt` is used

        Returns
        -------
        floating point (`dtype=np.float64`) representation of fixpoint input.
        """

        if y == "":
            return 0

        if frmt is None:
            frmt = self.frmt
        frmt = frmt.lower()

        if frmt == 'float':
            # this handles floats, np scalars + arrays and strings / string arrays
            try:
                y_float = np.float64(y)
            except ValueError:
                try:
                    y_float = np.complex(y).real
                except Exception as e:
                    y_float = None
                    logger.warning("Can't convert {0}: {1}".format(y, e))
            return y_float

        else:  # {'dec', 'bin', 'hex', 'csd'}
            # Find the number of places before the first radix point (if there is one)
            # and join integer and fractional parts
            # when returned string is empty, skip general conversions and rely on error handling
            # of individual routines
            # remove illegal characters and trailing zeros
            val_str = re.sub(self.FRMT_REGEX[frmt], r'', qstr(y)).lstrip('0')
            if len(val_str) > 0:

                val_str = val_str.replace(
                    ',', '.')  # ',' -> '.' for German-style numbers

                if val_str[
                        0] == '.':  # prepend '0' when the number starts with '.'
                    val_str = '0' + val_str
# TODO: This does not work with csd?!

                try:
                    int_str, frc_str = val_str.split(
                        '.')  # split into integer and fractional places
                except ValueError:  # no fractional part
                    int_str = val_str
                    frc_str = ''

                # count number of valid digits in string
                int_places = len(int_str) - 1
                frc_places = len(frc_str)
                raw_str = val_str.replace(
                    '.', '')  # join integer and fractional part

                logger.debug("y={0}, val_str={1}, raw_str={2} ".format(
                    y, val_str, raw_str))

            else:
                return 0.0

        # (1) calculate the decimal value of the input string using np.float64()
        #     which takes the number of decimal places into account.
        # (2) divide by scale
        if frmt == 'dec':
            # try to convert string -> float directly with decimal point position
            try:
                y_float = self.fixp(val_str, scaling='div')
            except Exception as e:
                logger.warning(e)
                y_float = None

        elif frmt in {'hex', 'bin'}:
            # - Glue integer and fractional part to a string without radix point
            # - Divide by <base> ** <number of fractional places> for correct scaling
            # - Strip MSBs outside fixpoint range
            # - Transform numbers in negative 2's complement to negative floats.
            # - Calculate the fixpoint representation for correct saturation / quantization
            try:
                y_dec = int(raw_str, self.base) / self.base**frc_places

                if y_dec == 0:  # avoid log2(0)
                    return 0

                int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0)
                # When number is outside fixpoint range, discard MSBs:
                if int_bits > self.WI + 1:
                    if frmt == 'hex':
                        raw_str = np.binary_repr(int(raw_str, 16))
                    raw_str = raw_str[int_bits - self.WI -
                                      1:]  # discard the upper bits

                    y_dec = int(raw_str,
                                2) / self.base**frc_places  # recalculate y_dec

                    if y_dec == 0:  # avoid log2(0)
                        return 0

                    int_bits = max(int(np.floor(np.log2(y_dec))) + 1,
                                   0)  # ... and int_bits
                # now, y_dec is in the correct range:
                if int_bits <= self.WI:  # positive number
                    pass
                elif int_bits == self.WI + 1:  # negative, calculate 2's complemente
                    y_dec = y_dec - (1 << int_bits)
                # quantize / saturate / wrap & scale the integer value:
                y_float = self.fixp(y_dec, scaling='div')
            except Exception as e:
                logger.warning(e)
                y_dec = None
                y_float = None

            logger.debug("MSB={0} | LSB={1} | scale={2}".format(
                self.MSB, self.LSB, self.scale))
            logger.debug("y_in={0} | y_dec={1}".format(y, y_dec))

        elif frmt == 'csd':
            # - Glue integer and fractional part to a string without radix point
            # - Divide by 2 ** <number of fractional places> for correct scaling

            y_float = csd2dec(raw_str)
            if y_float is not None:
                y_float = y_float / 2**frc_places

        else:
            logger.error('Unknown output format "%s"!'.format(frmt))
            y_float = None

        if frmt != "float":
            logger.debug("MSB={0:g} |  scale={1:g} | "
                         "y={2} | y_float={3}".format(self.MSB, self.scale, y,
                                                      y_float))

        if y_float is not None:
            return y_float
        else:
            return 0.0
Beispiel #30
0
    def fixp(self, y, scaling='mult'):
        """
        Return fixed-point integer or fractional representation for `y`
        (scalar or array-like) with the same shape as `y`.

        Saturation / two's complement wrapping happens outside the range +/- MSB,
        requantization (round, floor, fix, ...) is applied on the ratio `y / LSB`.

        Parameters
        ----------
        y: scalar or array-like object
            input value (floating point format) to be quantized

        scaling: String
            Determine the scaling before and after quantizing / saturation
            
            *'mult'* float in, int out: 
                `y` is multiplied by `self.scale` *before* quantizing / saturating
            **'div'**: int in, float out:
                `y` is divided by `self.scale` *after* quantizing / saturating.                
            **'multdiv'**: float in, float out (default):
                both of the above
             
            For all other settings, `y` is transformed unscaled.

        Returns
        -------
        float scalar or ndarray
            with the same shape as `y`, in the range
            `-2*self.MSB` ... `2*self.MSB-self.LSB`

        Examples:
        ---------

        >>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'}
        >>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa
        >>> myQa.resetN()  # reset overflow counter
        >>> a = np.arange(0,5, 0.05) # create input signal

        >>> aq = myQa.fixed(a) # quantize input signal
        >>> plt.plot(a, aq) # plot quantized vs. original signal
        >>> print(myQa.N_over, "overflows!") # print number of overflows

        >>> # Convert output to same format as input:
        >>> b = np.arange(200, dtype = np.int16)
        >>> btype = np.result_type(b)
        >>> # MSB = 2**7, LSB = 2**2:
        >>> q_obj_b = {'WI':7, 'WF':-2, 'ovfl':'wrap', 'quant':'round'}
        >>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb
        >>> bq = myQb.fixed(b)
        >>> bq = bq.astype(btype) # restore original variable type
        """

        #======================================================================
        # (1) : Convert input argument into proper floating point scalars /
        #        arrays and initialize flags
        #======================================================================
        if np.shape(y):
            # create empty arrays for result and overflows with same shape as y
            # for speedup, test for invalid types
            SCALAR = False
            y = np.asarray(y)  # convert lists / tuples / ... to numpy arrays
            yq = np.zeros(y.shape)
            over_pos = over_neg = np.zeros(y.shape, dtype=bool)
            self.ovr_flag = np.zeros(y.shape, dtype=int)

            if np.issubdtype(y.dtype, np.number):
                pass
            elif y.dtype.kind in {'U', 'S'}:  # string or unicode
                try:
                    y = y.astype(np.float64)  # try to convert to float
                except (TypeError, ValueError):
                    try:
                        np.char.replace(y, ' ', '')  # remove all whitespace
                        y = y.astype(complex)  # try to convert to complex
                    except (TypeError, ValueError
                            ) as e:  # try converting elements recursively
                        y = list(
                            map(
                                lambda y_scalar: self.fixp(y_scalar,
                                                           scaling=scaling),
                                y))
            else:
                logger.error("Argument '{0}' is of type '{1}',\n"
                             "cannot convert to float.".format(y, y.dtype))
                y = np.zeros(y.shape)
        else:
            SCALAR = True
            # get rid of errors that have occurred upstream
            if y is None or str(y) == "":
                y = 0
            # If y is not a number, convert to string, remove whitespace and convert
            # to complex format:
            elif not np.issubdtype(type(y), np.number):
                y = qstr(y)
                y = y.replace(' ', '')  # remove all whitespace
                try:
                    y = float(y)
                except (TypeError, ValueError):
                    try:
                        y = complex(y)
                    except (TypeError, ValueError) as e:
                        logger.error("Argument '{0}' yields \n {1}".format(
                            y, e))
                        y = 0.0
            over_pos = over_neg = yq = 0
            self.ovr_flag = 0

        # convert pseudo-complex (imag = 0) and complex values to real
        y = np.real_if_close(y)
        if np.iscomplexobj(y):
            logger.warning(
                "Casting complex values to real before quantization!")
            # quantizing complex objects is not supported yet
            y = y.real

        scaling = scaling.lower()
        y_in = y  # y before scaling / quantizing
        #======================================================================
        # (2) : Multiply by `scale` factor before requantization and saturation
        #       when `scaling=='mult'`or 'multdiv'
        #======================================================================
        y = y / self.LSB
        if scaling in {'mult', 'multdiv'}:
            y = y * self.scale

        #======================================================================
        # (3) : Divide by LSB and apply selected quantization method to convert
        #       floating point inputs to "fixpoint integers" arrays
        #       Next, multiply by LSB to restore original scale
        #=====================================================================
        if self.quant == 'floor':
            yq = np.floor(y)
            # largest integer i, such that i <= x (= binary truncation)
        elif self.quant == 'round':
            yq = np.round(y)
            # rounding, also = binary rounding
        elif self.quant == 'fix':
            yq = np.fix(y)
            # round to nearest integer towards zero ("Betragsschneiden")
        elif self.quant == 'ceil':
            yq = np.ceil(y)
            # smallest integer i, such that i >= x
        elif self.quant == 'rint':
            yq = np.rint(y)
            # round towards nearest int
        elif self.quant == 'none':
            yq = y
            # return unquantized value
        else:
            raise Exception('Unknown Requantization type "%s"!' % (self.quant))

        yq = yq * self.LSB
        logger.debug("y_in={0} | y={1} | yq={2}".format(y_in, y, yq))

        #======================================================================
        # (4) : Handle Overflow / saturation in relation to MSB
        #=====================================================================

        if self.ovfl == 'none':
            pass
        else:
            # Bool. vectors with '1' for every neg./pos overflow:
            over_neg = (yq < self.MIN)
            over_pos = (yq > self.MAX)
            # create flag / array of flags for pos. / neg. overflows
            self.ovr_flag = over_pos.astype(int) - over_neg.astype(int)
            # No. of pos. / neg. / all overflows occured since last reset:
            self.N_over_neg += np.sum(over_neg)
            self.N_over_pos += np.sum(over_pos)
            self.N_over = self.N_over_neg + self.N_over_pos

            # Replace overflows with Min/Max-Values (saturation):
            if self.ovfl == 'sat':
                yq = np.where(over_pos, self.MAX, yq)  # (cond, true, false)
                yq = np.where(over_neg, self.MIN, yq)
            # Replace overflows by two's complement wraparound (wrap)
            elif self.ovfl == 'wrap':
                yq = np.where(
                    over_pos | over_neg, yq - 4. * self.MSB * np.fix(
                        (np.sign(yq) * 2 * self.MSB + yq) / (4 * self.MSB)),
                    yq)
            else:
                raise Exception('Unknown overflow type "%s"!' % (self.ovfl))
                return None
        #======================================================================
        # (5) : Divide result by `scale` factor when `scaling=='div'`or 'multdiv'
        #       - frmt2float()
        #       - filter_coeffs when quantizing the coefficients
        #       float2frmt passes on the scaling argument
        #======================================================================

        if scaling in {'div', 'multdiv'}:
            yq = yq / self.scale

        if SCALAR and isinstance(yq, np.ndarray):
            yq = yq.item()  # convert singleton array to scalar

        return yq
Beispiel #31
0
    def frmt2float(self, y, frmt=None):
        """
        Return floating point representation for fixpoint scalar `y` given in
        format `frmt`.

        * When input format is float, return unchanged
        * else:
            - Remove illegal characters and leading '0's
            - Count number of fractional places `frc_places` and remove radix point
            - Calculate decimal, fractional representation `y_dec` of string,
              using the base and the number of fractional places
            (- Calculate two's complement for `W` bits for negative bin and hex numbers)
            - Calculate fixpoint float representation `y_float = fixp(y_dec, scaling='div')`,
              dividing the result by `scale`.

        Parameters
        ----------
        y: scalar or string
            to be quantized with the numeric base specified by `frmt`.

        frmt: string (optional)
            any of the formats `float`, `dec`, `bin`, `hex`, `csd`)
            When `frmt` is unspecified, the instance parameter `self.frmt` is used

        Returns
        -------
        quantized floating point (`dtype=np.float64`) representation of input string
        """
        if y == "":
            return 0

        if frmt is None:
            frmt = self.frmt
        frmt = frmt.lower()
        y_float = y_dec = None

        if frmt == 'float32':
            float_frmt = np.float32
        elif frmt == 'float16':
            float_frmt = np.float16

        if frmt == 'float':
            # this handles floats, np scalars + arrays and strings / string arrays
            try:
                y_float = np.float64(y)
            except ValueError:
                try:
                    y_float = np.complex(y).real
                except Exception as e:
                    y_float = None
                    logger.warning("Can't convert {0}: {1}".format(y, e))
            return y_float

        else:  # {'dec', 'bin', 'hex', 'csd'}
            # Find the number of places before the first radix point (if there is one)
            # and join integer and fractional parts
            # when returned string is empty, skip general conversions and rely on error handling
            # of individual routines
            # remove illegal characters and trailing zeros
            val_str = re.sub(self.FRMT_REGEX[frmt], r'', qstr(y)).lstrip('0')
            if len(val_str) > 0:
                val_str = val_str.replace(
                    ',', '.')  # ',' -> '.' for German-style numbers
                if val_str[
                        0] == '.':  # prepend '0' when the number starts with '.'
                    val_str = '0' + val_str

                # count number of fractional places in string
                try:
                    _, frc_str = val_str.split(
                        '.')  # split into integer and fractional places
                    frc_places = len(frc_str)
                except ValueError:  # no fractional part
                    frc_places = 0

                raw_str = val_str.replace(
                    '.', '')  # join integer and fractional part

                logger.debug("y={0}, val_str={1}, raw_str={2} ".format(
                    y, val_str, raw_str))
            else:
                return 0.0

        # (1) calculate the decimal value of the input string using np.float64()
        #     which takes the number of decimal places into account.
        # (2) quantize and saturate
        # (3) divide by scale
        if frmt == 'dec':
            # try to convert string -> float directly with decimal point position
            try:
                y_dec = y_float = self.fixp(val_str, scaling='div')
            except Exception as e:
                logger.warning(e)

        elif frmt in {'hex', 'bin'}:
            # - Glue integer and fractional part to a string without radix point
            # - Check for a negative sign, use this information only in the end
            # - Divide by <base> ** <number of fractional places> for correct scaling
            # - Strip MSBs outside fixpoint range
            # - Transform numbers in negative 2's complement to negative floats.
            # - Calculate the fixpoint representation for correct saturation / quantization
            neg_sign = False
            try:
                if raw_str[0] == '-':
                    neg_sign = True
                    raw_str = raw_str.lstrip('-')

                y_dec = abs(int(raw_str, self.base) / self.base**frc_places)

                if y_dec == 0:  # avoid log2(0)
                    return 0

                int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0)
                # When number is outside fixpoint range, discard MSBs:
                if int_bits > self.WI + 1:
                    if frmt == 'hex':
                        raw_str = np.binary_repr(int(raw_str, 16))
                    # discard the upper bits outside the valid range
                    raw_str = raw_str[int_bits - self.WI - 1:]

                    # recalculate y_dec for truncated string
                    y_dec = int(raw_str, 2) / self.base**frc_places

                    if y_dec == 0:  # avoid log2(0) error in code below
                        return 0

                    int_bits = max(int(np.floor(np.log2(y_dec))) + 1,
                                   0)  # ... and int_bits
                # now, y_dec is in the correct range:
                if int_bits <= self.WI:  # positive number
                    pass
                elif int_bits == self.WI + 1:  # negative, calculate 2's complemente
                    y_dec = y_dec - (1 << int_bits)
                # quantize / saturate / wrap & scale the integer value:
                if neg_sign:
                    y_dec = -y_dec
                y_float = self.fixp(y_dec, scaling='div')
            except Exception as e:
                logger.warning(e)
                y_dec = y_float = None

            logger.debug("MSB={0} | LSB={1} | scale={2}".format(
                self.MSB, self.LSB, self.scale))
            logger.debug("y_in={0} | y_dec={1}".format(y, y_dec))
        # ----
        elif frmt == 'csd':
            # - Glue integer and fractional part to a string without radix point
            # - Divide by 2 ** <number of fractional places> for correct scaling
            # - Calculate fixpoint representation for saturation / overflow effects

            y_dec = csd2dec_vec(raw_str)  # csd -> integer
            if y_dec is not None:
                y_float = self.fixp(y_dec / 2**frc_places, scaling='div')
        # ----
        else:
            logger.error('Unknown output format "%s"!'.format(frmt))

        if frmt != "float":
            logger.debug("MSB={0:g} |  scale={1:g} | raw_str={2} | val_str={3}"\
                         .format(self.MSB, self.scale, raw_str, val_str))
            logger.debug("y={0} | y_dec = {1} | y_float={2}".format(
                y, y_dec, y_float))

        if y_float is not None:
            return y_float
        else:
            return 0.0
Beispiel #32
0
 def displayText(self, text, locale):
     return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), 
             n_digits = params['FMT_pz'])
Beispiel #33
0
    def frmt2float(self, y, frmt=None):
        """
        Return floating point representation for fixpoint scalar `y` given in
        format `frmt`.

        - Construct string representation without radix point, count number of
          fractional places.
        - Calculate integer representation of string, taking the base into account
        (- When result is negative, calculate two's complement for `W` bits)
        - Scale with `2** -W`
        - Scale with the number of fractional places (depending on format!)

        Parameters
        ----------
        y: scalar or string
            to be quantized with the numeric base specified by `frmt`.

        frmt: string (optional)
            any of the formats `float`, `dec`, `bin`, `hex`, `csd`)
            When `frmt` is unspecified, the instance parameter `self.frmt` is used

        Returns
        -------
        floating point (`dtype=np.float64`) representation of fixpoint input.
        """

        if y == "":
            return 0

        if frmt is None:
            frmt = self.frmt
        frmt = frmt.lower()

        if frmt == 'float':
            # this handles floats, np scalars + arrays and strings / string arrays
            try:
                y_float = np.float64(y)
            except ValueError:
                try:
                    y_float = np.complex(y).real
                except Exception as e:
                    y_float = None
                    logger.warning("Can't convert {0}: {1}".format(y,e))
            return y_float

        else: # {'dec', 'bin', 'hex', 'csd'}
         # Find the number of places before the first radix point (if there is one)
         # and join integer and fractional parts
         # when returned string is empty, skip general conversions and rely on error handling
         # of individual routines
         # remove illegal characters and trailing zeros
            val_str = re.sub(self.FRMT_REGEX[frmt],r'', qstr(y)).lstrip('0')
            if len(val_str) > 0:

                val_str = val_str.replace(',','.') # ',' -> '.' for German-style numbers

                if val_str[0] == '.': # prepend '0' when the number starts with '.'
                    val_str = '0' + val_str
# TODO: This does not work with csd?!

                try:
                    int_str, frc_str = val_str.split('.') # split into integer and fractional places
                except ValueError: # no fractional part
                    int_str = val_str
                    frc_str = ''

                # count number of valid digits in string
                int_places = len(int_str)-1
                frc_places = len(frc_str)
                raw_str = val_str.replace('.','') # join integer and fractional part

                logger.debug("y={0}, val_str={1}, raw_str={2} ".format(y, val_str, raw_str))

            else:
                return 0.0


        # (1) calculate the decimal value of the input string using np.float64()
        #     which takes the number of decimal places into account.
        # (2) divide by scale
        if frmt == 'dec':
            # try to convert string -> float directly with decimal point position
            try:
                y_float = self.fixp(val_str, scaling='div')
            except Exception as e:
                logger.warning(e)
                y_float = None

        elif frmt in {'hex', 'bin'}:
            # - Glue integer and fractional part to a string without radix point
            # - Divide by <base> ** <number of fractional places> for correct scaling
            # - Strip MSBs outside fixpoint range
            # - Transform numbers in negative 2's complement to negative floats.
            # - Calculate the fixpoint representation for correct saturation / quantization
            try:
                y_dec = int(raw_str, self.base) / self.base**frc_places

                if y_dec == 0:  # avoid log2(0)
                    return 0

                int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0)
                # When number is outside fixpoint range, discard MSBs:
                if int_bits > self.WI + 1:
                    if frmt == 'hex':
                        raw_str = np.binary_repr(int(raw_str, 16))
                    raw_str = raw_str[int_bits - self.WI - 1:] # discard the upper bits

                    y_dec = int(raw_str, 2) / self.base**frc_places # recalculate y_dec

                    if y_dec == 0: # avoid log2(0)
                        return 0

                    int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # ... and int_bits
                # now, y_dec is in the correct range:
                if int_bits <= self.WI: # positive number
                    pass
                elif int_bits == self.WI + 1: # negative, calculate 2's complemente
                    y_dec = y_dec - (1 << int_bits)
                # quantize / saturate / wrap & scale the integer value:
                y_float = self.fixp(y_dec, scaling='div')
            except Exception as e:
                logger.warning(e)
                y_dec = None
                y_float = None

            logger.debug("MSB={0} | LSB={1} | scale={2}".format(self.MSB, self.LSB, self.scale))
            logger.debug("y_in={0} | y_dec={1}".format(y, y_dec))

        elif frmt == 'csd':
            # - Glue integer and fractional part to a string without radix point
            # - Divide by 2 ** <number of fractional places> for correct scaling

            y_float = csd2dec(raw_str)
            if y_float is not None:
                y_float = y_float / 2**frc_places

        else:
            logger.error('Unknown output format "%s"!'.format(frmt))
            y_float = None

        if frmt != "float": logger.debug("MSB={0:g} |  scale={1:g} | "
              "y={2} | y_float={3}".format(self.MSB, self.scale, y, y_float))

        if y_float is not None:
            return y_float
        else:
            return 0.0
Beispiel #34
0
    def fixp(self, y, scaling='mult'):
        """
        Return fixed-point integer or fractional representation for `y`
        (scalar or array-like) with the same shape as `y`.

        Saturation / two's complement wrapping happens outside the range +/- MSB,
        requantization (round, floor, fix, ...) is applied on the ratio `y / LSB`.

        Parameters
        ----------
        y: scalar or array-like object
            in floating point format to be quantized

        scaling: String
            When `scaling='mult'` (default), `y` is multiplied by `self.scale` before
            requantizing and saturating, when `scaling='div'`,
            `y` is divided by `self.scale`. For all other settings, `y` is transformed
            unscaled.

        Returns
        -------
        float scalar or ndarray
            with the same shape as `y`, in the range
            `-2*self.MSB` ... `2*self.MSB-self.LSB`

        Examples:
        ---------

        >>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'}
        >>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa
        >>> myQa.resetN()  # reset overflow counter
        >>> a = np.arange(0,5, 0.05) # create input signal

        >>> aq = myQa.fixed(a) # quantize input signal
        >>> plt.plot(a, aq) # plot quantized vs. original signal
        >>> print(myQa.N_over, "overflows!") # print number of overflows

        >>> # Convert output to same format as input:
        >>> b = np.arange(200, dtype = np.int16)
        >>> btype = np.result_type(b)
        >>> # MSB = 2**7, LSB = 2**2:
        >>> q_obj_b = {'WI':7, 'WF':-2, 'ovfl':'wrap', 'quant':'round'}
        >>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb
        >>> bq = myQb.fixed(b)
        >>> bq = bq.astype(btype) # restore original variable type
        """

        if np.shape(y):
            # create empty arrays for result and overflows with same shape as y
            # for speedup, test for invalid types
            SCALAR = False
            y = np.asarray(y) # convert lists / tuples / ... to numpy arrays
            yq = np.zeros(y.shape)
            over_pos = over_neg = np.zeros(y.shape, dtype = bool)
            self.ovr_flag = np.zeros(y.shape, dtype = int)

            if np.issubdtype(y.dtype, np.number):
                pass
            elif y.dtype.kind in {'U', 'S'}: # string or unicode
                try:
                    y = y.astype(np.float64) # try to convert to float
                except (TypeError, ValueError):
                    try:
                        np.char.replace(y, ' ', '') # remove all whitespace
                        y = y.astype(complex) # try to convert to complex
                    except (TypeError, ValueError) as e: # try converting elements individually
                        y = list(map(lambda y_scalar:
                            self.fixp(y_scalar, scaling=scaling), y))

            else:
                logger.error("Argument '{0}' is of type '{1}',\n"
                             "cannot convert to float.".format(y, y.dtype))
                y = np.zeros(y.shape)
        else:
            SCALAR = True
            # get rid of errors that have occurred upstream
            if y is None or str(y) == "":
                y = 0
            # If y is not a number, convert to string, remove whitespace and convert
            # to complex format:
            elif not np.issubdtype(type(y), np.number):
                y = qstr(y)
                y = y.replace(' ','') # remove all whitespace
                try:
                    y = float(y)
                except (TypeError, ValueError):
                    try:
                        y = complex(y)
                    except (TypeError, ValueError) as e:
                        logger.error("Argument '{0}' yields \n {1}".format(y,e))
                        y = 0.0
            over_pos = over_neg = yq = 0
            self.ovr_flag = 0

        # convert pseudo-complex (imag = 0) and complex values to real
        y = np.real_if_close(y)
        if np.iscomplexobj(y):
            logger.warning("Casting complex values to real before quantization!")
            # quantizing complex objects is not supported yet
            y = y.real

        y_in = y # y before scaling
        # convert to "fixpoint integer" for requantizing in relation to LSB
        y = y / self.LSB
        if scaling == 'mult':
            y = y * self.scale

        if   self.quant == 'floor':  yq = np.floor(y)
             # largest integer i, such that i <= x (= binary truncation)
        elif self.quant == 'round':  yq = np.round(y)
             # rounding, also = binary rounding
        elif self.quant == 'fix':    yq = np.fix(y)
             # round to nearest integer towards zero ("Betragsschneiden")
        elif self.quant == 'ceil':   yq = np.ceil(y)
             # smallest integer i, such that i >= x
        elif self.quant == 'rint':   yq = np.rint(y)
             # round towards nearest int
        elif self.quant == 'none':   yq = y
            # return unquantized value
        else:
            raise Exception('Unknown Requantization type "%s"!'%(self.quant))

        # revert to original fractional scale
        yq = yq * self.LSB

        logger.debug("y_in={0} | y={1} | yq={2}".format(y_in, y, yq))

        # Handle Overflow / saturation in relation to MSB
        if   self.ovfl == 'none':
            pass
        else:
            # Bool. vectors with '1' for every neg./pos overflow:
            over_neg = (yq < self.MIN)
            over_pos = (yq > self.MAX)
            # create flag / array of flags for pos. / neg. overflows
            self.ovr_flag = over_pos.astype(int) - over_neg.astype(int)
            # No. of pos. / neg. / all overflows occured since last reset:
            self.N_over_neg += np.sum(over_neg)
            self.N_over_pos += np.sum(over_pos)
            self.N_over = self.N_over_neg + self.N_over_pos

            # Replace overflows with Min/Max-Values (saturation):
            if self.ovfl == 'sat':
                yq = np.where(over_pos, self.MAX, yq) # (cond, true, false)
                yq = np.where(over_neg, self.MIN, yq)
            # Replace overflows by two's complement wraparound (wrap)
            elif self.ovfl == 'wrap':
                yq = np.where(over_pos | over_neg,
                    yq - 4. * self.MSB*np.fix((np.sign(yq) * 2 * self.MSB+yq)/(4*self.MSB)), yq)
            else:
                raise Exception('Unknown overflow type "%s"!'%(self.ovfl))
                return None

        if scaling == 'div':
            yq = yq / self.scale

        if SCALAR and isinstance(yq, np.ndarray):
            yq = yq.item() # convert singleton array to scalar

        return yq