Exemplo n.º 1
0
def scale_int(s, config, num_overlaps):
    shape = config['shape']
    ext = feature_extent(s, config)
    #introduce noise to ext
    ext_noise = config['ext_noise']
    ext = np.random.normal(ext, ext_noise*ext)
    extsize = ext*2
    shapesize = shape[0]
    if extsize <= shapesize:
        scale = 1
    else:
        scale = int(np.floor(extsize/shapesize) + 1)
    newshape = [i * scale for i in shape]
    holo = LMHologram(coordinates=coordinates(newshape))
    holo.instrument.properties = config['instrument']
    # ... calculate hologram
    frame = np.random.normal(0, config['noise'], newshape)
    holo.particle = s
    holo.particle.x_p += (scale-1)*100
    holo.particle.y_p += (scale-1)*100
    holo.particle = add_overlaps(ext, num_overlaps, config).append(holo.particle)
    frame += holo.hologram().reshape(newshape)
    frame = np.clip(100 * frame, 0, 255).astype(np.uint8)
    #decimate
    frame = frame[::scale, ::scale]
    return frame, scale
Exemplo n.º 2
0
def feature_extent(sphere, instrument, nfringes=20, maxrange=300):
    '''Radius of holographic feature in pixels'''

    x = np.arange(0, maxrange)
    h = LMHologram(coordinates=x, instrument=instrument)
    h.particle.a_p = sphere.a_p
    h.particle.n_p = sphere.n_p
    h.particle.z_p = sphere.z_p
    b = h.hologram() - 1.
    ndx = np.where(np.diff(np.sign(b)))[0] + 1
    extent = maxrange if (len(ndx) <= nfringes) else float(ndx[nfringes])
    return extent
Exemplo n.º 3
0
 def setupTheory(self, percentpix):
     # Profile and Frame use the same particle and instrument
     self.particle = Sphere()
     self.instrument = Instrument()
     # Theory for radial profile
     self.theory = LMHologram(particle=self.particle,
                              instrument=self.instrument)
     self.theory.coordinates = np.arange(self.maxrange)
     # Theory for image
     self.frame = Frame(particle=self.particle,
                        instrument=self.instrument,
                        percentpix=percentpix)
Exemplo n.º 4
0
def makedata(config={}):
    '''Make Training Data'''
    # set up pipeline for hologram calculation
    shape = config['shape']
    holo = LMHologram(coordinates=coordinates(shape))
    holo.instrument.properties = config['instrument']

    # create directories and filenames
    directory = os.path.expanduser(config['directory'])
    imgtype = config['imgtype']

    nframes = config['nframes']
    start = 0
    tempnum = nframes
    for dir in ('images', 'labels', 'params'):
        path = os.path.join(directory, dir)
        if not os.path.exists(path):
            os.makedirs(path)
        already_files = len(os.listdir(path))
        if already_files < tempnum:  #if there are fewer than the number of files desired
            tempnum = already_files
    if not config['overwrite']:
        start = tempnum
        if start >= nframes:
            return
    with open(directory + '/config.json', 'w') as f:
        json.dump(config, f)
    filetxtname = os.path.join(directory, 'filenames.txt')
    imgname = os.path.join(directory, 'images', 'image{:05d}.' + imgtype)
    jsonname = os.path.join(directory, 'params', 'image{:05d}.json')
    yoloname = os.path.join(directory, 'labels' , 'image{:05d}.txt')

    filetxt = open(filetxtname, 'w')
    for n in range(start, nframes):  # for each frame ...
        print(imgname.format(n))
        sample = make_sample(config)   # ... get params for particles
        # ... calculate hologram
        frame = np.random.normal(1., config['noise'], shape)
        if len(sample) > 0:
            holo.particle = sample
            frame += holo.hologram().reshape(shape) - 1.
        frame = np.clip(100 * frame, 0, 255).astype(np.uint8)
        # ... and save the results
        cv2.imwrite(imgname.format(n), frame)
        with open(jsonname.format(n), 'w') as fp:
            fp.write(format_json(sample, config))
        with open(yoloname.format(n), 'w') as fp:
            fp.write(format_yolo(sample, config))
        filetxt.write(imgname.format(n) + '\n')
        #print('finished image {}'.format(n+1))
    return
Exemplo n.º 5
0
def feature_extent(a_p, n_p, z_p, config, nfringes=20, maxrange=300):
    '''Radius of holographic feature in pixels'''

    h = LMHologram(coordinates=np.arange(maxrange))
    h.instrument.properties = config
    h.particle.a_p = a_p
    h.particle.n_p = n_p
    h.particle.z_p = z_p
    # roughly estimate radii of zero crossings
    b = h.hologram() - 1.
    ndx = np.where(np.diff(np.sign(b)))[0] + 1
    if len(ndx) <= nfringes:
        return maxrange
    else:
        return float(ndx[nfringes])
Exemplo n.º 6
0
    def deserialize(self, info):
        '''
        Restore serialized state of Feature from dict

        Arguments
        ---------
        info: dict | str
            Restore keyword/value pairs from dict.
            Alternatively restore dict from named file.
        '''
        if info is None:
            return
        if isinstance(info, str):
            with open(info, 'rb') as f:
                info = json.load(f)
        if 'model' in info.keys():
            if info['model'] == 'LMHologram':
                self.model = LMHologram()
                self.model.properties = {
                    k: info[k]
                    for k in self.model.properties.keys()
                }
        if 'coordinates' in info.keys():
            if hasattr(self.model, 'coordinates'):
                args = info['coordinates']
                self.model.coordinates = coordinates(*args)
        if 'data' in info.keys():
            data = np.array(info['data'])
            if 'shape' in info.keys():
                data = data.reshape(info['shape'])
            self.data = data
        if 'label' in info.keys():
            self.label = info['label']
Exemplo n.º 7
0
def fit(data, a_p, n_p, z_p, plot=False, return_img=False):
    feature = Feature(model=LMHologram())
    px = int(np.sqrt(data.size))

    ins = feature.model.instrument
    ins.wavelength = wv
    ins.magnification = mag
    ins.n_m = n_m

    feature.optimizer.mask.settings['distribution'] = 'fast'
    feature.optimizer.mask.settings['percentpix'] = .1

    feature.model.coordinates = coordinates((px, px), dtype=np.float32)
    p = feature.model.particle

    p.r_p = [px // 2, px // 2, z_p / mag]
    p.a_p = a_p
    p.n_p = n_p
    feature.data = np.array(data)
    result = feature.optimize(method='lm', verbose=False)
    print(feature.model.hologram().shape)
    print(result)
    if plot:
        plt.imshow(np.hstack([data, feature.model.hologram().reshape(shape)]))
        plt.show()
    a_fit = feature.model.particle.a_p
    n_fit = feature.model.particle.n_p
    z_fit = feature.model.particle.z_p

    if return_img:
        return feature.model.hologram(), a_fit, n_fit, z_fit
    else:
        return a_fit, n_fit, z_fit
Exemplo n.º 8
0
def mtd(configfile='mtd.json'):
    '''Make Training Data'''
    # read configuration
    with open(configfile, 'r') as f:
        config = json.load(f)

    # set up pipeline for hologram calculation
    shape = config['shape']
    holo = LMHologram(coordinates=coordinates(shape))
    holo.instrument.properties = config['instrument']

    # create directories and filenames
    directory = os.path.expanduser(config['directory'])
    imgtype = config['imgtype']
    for dir in ('images_labels', 'params'):
        if not os.path.exists(os.path.join(directory, dir)):
            os.makedirs(os.path.join(directory, dir))
    shutil.copy2(configfile, directory)
    filetxtname = os.path.join(directory, 'filenames.txt')
    imgname = os.path.join(directory, 'images_labels',
                           'image{:04d}.' + imgtype)
    jsonname = os.path.join(directory, 'params', 'image{:04d}.json')
    yoloname = os.path.join(directory, 'images_labels', 'image{:04d}.txt')

    filetxt = open(filetxtname, 'w')
    for n in range(config['nframes']):  # for each frame ...
        print(imgname.format(n))
        sample = make_sample(config)  # ... get params for particles
        # ... calculate hologram
        frame = np.random.normal(0, config['noise'], shape)
        if len(sample) > 0:
            holo.particle = sample
            frame += holo.hologram().reshape(shape)
        else:
            frame += 1.
        frame = np.clip(100 * frame, 0, 255).astype(np.uint8)
        # ... and save the results
        cv2.imwrite(imgname.format(n), frame)
        with open(jsonname.format(n), 'w') as fp:
            fp.write(format_json(sample, config))
        with open(yoloname.format(n), 'w') as fp:
            fp.write(format_yolo(sample, config))
        filetxt.write(imgname.format(n) + '\n')
Exemplo n.º 9
0
 def setupTheory(self):
     self.profile_coords = np.arange(self.maxrange)
     self._coordinates = self.profile_coords
     self.feature = Feature(model=LMHologram())
     self.theory = self.feature.model
     self.theory.coordinates = self.coordinates
     self.theory.instrument.wavelength = self.ui.wavelength.value()
     self.theory.instrument.magnification = self.ui.magnification.value()
     self.theory.instrument.n_m = self.ui.n_m.value()
     self.theory.particle.a_p = self.ui.a_p.value()
     self.theory.particle.n_p = self.ui.n_p.value()
     self.theory.particle.z_p = self.ui.z_p.value()
     self.updateTheoryProfile()
Exemplo n.º 10
0
def feature_extent(sphere, config, nfringes=20, maxrange=300):
    '''Radius of holographic feature in pixels'''

    x = np.arange(0, maxrange)
    y = np.arange(0, maxrange)
    xv, yv = np.meshgrid(x, y)
    xv = xv.flatten()
    yv = yv.flatten()
    zv = np.zeros_like(xv)
    coordinates = np.stack((xv, yv, zv))
    h = LMHologram(coordinates=coordinates)
    h.instrument.properties = config['instrument']
    h.particle.a_p = sphere.a_p
    h.particle.n_p = sphere.n_p
    h.particle.z_p = sphere.z_p
    # roughly estimate radii of zero crossings
    b = h.hologram() - 1.
    ndx = np.where(np.diff(np.sign(b)))[0] + 1
    if len(ndx) <= nfringes:
        return maxrange
    else:
        return float(ndx[nfringes])
Exemplo n.º 11
0
 def __init__(self,
              data=None,
              coordinates=None,
              model=None,
              fixed=None,
              **kwargs):
     self._coordinates = None
     self.mask = Mask(**kwargs)
     self.model = model or LMHologram(**kwargs)
     self.data = data
     self.coordinates = coordinates
     self.estimator = Estimator(feature=self, **kwargs)
     self.optimizer = Optimizer(model=self.model, **kwargs)
     self.optimizer.fixed = fixed or [*self.optimizer.fixed,
                                      *self.model.aberrations.properties]
Exemplo n.º 12
0
def scale_float(s, config, num_overlaps):
    shape = config['shape']
    ext = feature_extent(s, config)
    #introduce noise to ext
    ext_noise = config['ext_noise']
    ext = np.random.normal(ext, ext_noise*ext)
    extsize = ext*2
    shapesize = shape[0]
    scale = float(extsize)/float(shapesize)
    newshape = [int(extsize)]*2
    holo = LMHologram(coordinates=coordinates(newshape))
    holo.instrument.properties = config['instrument']
    # ... calculate hologram
    frame = np.random.normal(0, config['noise'], newshape)
    s.x_p += (scale-1)*100.
    s.y_p += (scale-1)*100.
    totalspheres = add_overlaps(ext, num_overlaps, config)
    totalspheres.append(s)
    holo.lorenzmie.particle = totalspheres
    frame += holo.hologram().reshape(newshape)
    frame = np.clip(100 * frame, 0, 255).astype(np.uint8)
    #reshape
    #frame = cv2.resize(frame, tuple(shape))
    return frame, scale
Exemplo n.º 13
0
def fit(data, a_p, n_p, z_p, plot=False, return_img=False, percentpix=0.1):
    feature = Feature(model=LMHologram())
    px = int(np.sqrt(data.size))

    ins = feature.model.instrument
    ins.wavelength = wv
    ins.magnification = mag
    ins.n_m = n_m

    #feature.mask.distribution = 'fast'
    #feature.mask.percentpix = percentpix

    x = np.arange(0, px)
    y = np.arange(0, px)
    xv, yv = np.meshgrid(x, y)
    xv = xv.flatten()
    yv = yv.flatten()
    zv = np.zeros_like(xv)
    coordinates = np.stack((xv, yv, zv))

    #feature.model.coordinates = coordinates((px, px), dtype=np.float32)
    feature.model.coordinates = coordinates
    feature.coordinates = coordinates
    p = feature.particle

    p.r_p = [px // 2, px // 2, z_p / mag]
    p.a_p = a_p
    p.n_p = n_p
    feature.data = np.array(data) / np.mean(data)
    #result = feature.optimize(method='lm', verbose=False)
    result = feature.optimize()
    print(result)
    if plot:
        plt.imshow(np.hstack([data, feature.hologram()]))
        plt.show()
    a_fit = feature.model.particle.a_p
    n_fit = feature.model.particle.n_p
    z_fit = feature.model.particle.z_p

    if return_img:
        return feature.hologram(), a_fit, n_fit, z_fit
    else:
        return a_fit, n_fit, z_fit
Exemplo n.º 14
0
from CATCH.CATCHobject import crop_frame
from pylorenzmie.utilities.visualization import report
import cv2
import ast
import pandas as pd
import numpy as np

df = pd.read_csv('results_file.csv') #specify your ML preds file here
df['bbox'] = df['bbox'].apply(ast.literal_eval) #convert csv string to tuple

df = df[df['edge']==False] #remove features near the edge (optional)


refined = []
for index, row in df.iterrows():
    f = Feature(model=LMHologram(double_precision=False))
    
    # Instrument configuration
    ins = f.model.instrument
    ins.wavelength = 0.447     # [um]
    ins.magnification = 0.048  # [um/pixel]
    ins.n_m = 1.34

    #provide initial parameter estimates from ML
    p = f.particle
    p.properties = row

    #crop experimental data and give it to feature
    frame = cv2.imread(row.framepath, cv2.IMREAD_GRAYSCALE)
    crop = crop_frame(frame, [row])[0]
    
Exemplo n.º 15
0
            data = np.array(info['data'])
            if 'shape' in info.keys():
                data = data.reshape(info['shape'])
            self.data = data
        if 'label' in info.keys():
            self.label = info['label']


if __name__ == '__main__':
    from pylorenzmie.theory import coordinates
    #from pylorenzmie.theory.cuholo import cucoordinates as coordinates
    import cv2
    import matplotlib.pyplot as plt
    from time import time

    a = Feature(model=LMHologram())

    # Read example image
    img = cv2.imread('../tutorials/crop.png')
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = img / np.mean(img)
    shape = img.shape
    a.data = img

    # Instrument configuration
    a.model.coordinates = coordinates(shape, dtype=np.float32)
    ins = a.model.instrument
    ins.wavelength = 0.447
    ins.magnification = 0.048
    ins.n_m = 1.34
Exemplo n.º 16
0
def crop_feature(img_list=[], xy_preds=[], new_shape=(201, 201)):
    '''
    img_list: list of images (np.ndarray) with shape: old_shape
    xy_preds is the output of a yolo prediction: list of list of dicts
    xy_preds[i] corresponds to img_list[i]


    output:
    list of list of feature objects
    '''

    numfiles = len(img_list)
    numpreds = len(xy_preds)
    if numfiles != numpreds:
        raise Exception(
            'Number of images: {} does not match number of predictions: {}'.
            format(numfiles, numpreds))

    frame_list = []
    est_input_img = []
    est_input_scale = []
    for num in range(numfiles):
        feature_list = []
        img_local = img_list[num]
        preds_local = xy_preds[num]
        for pred in preds_local:
            f = Feature(model=LMHologram())
            conf = pred["conf"] * 100
            (x, y, w, h) = pred["bbox"]
            xc = int(np.round(x))
            yc = int(np.round(y))
            ext = np.amax([int(w), int(h)])
            if ext <= new_shape[0]:
                crop_shape = new_shape
                scale = 1
            else:
                scale = int(np.floor(ext / new_shape[0]) + 1)
                crop_shape = np.multiply(new_shape, scale)
            cropped, corner1 = crop_center(img_local, (xc, yc), crop_shape)
            cropped = cropped[:, :, 0]
            est_img = cropped[::scale, ::scale]
            est_input_img.append(est_img)
            est_input_scale.append(scale)
            newcenter = [int(x) for x in np.divide(crop_shape, 2)]
            ext_shape = (ext, ext)
            data, corner2 = crop_center(cropped, newcenter, ext_shape)
            corner = np.add(corner1, corner2)
            data = np.array(data) / 100.
            f.data = data
            coords = coordinates(shape=ext_shape, corner=corner)
            f.coordinates = coords
            f.model.particle.x_p = x
            f.model.particle.y_p = y
            feature_list.append(f)
        feature_list = np.array(feature_list)
        frame_list.append(feature_list)
    frame_list = np.array(frame_list)
    frlistsize = 0
    for frame in frame_list:
        frlistsize += len(frame)
    est_input_img = np.array(est_input_img)
    est_input_scale = np.array(est_input_scale)
    if frlistsize != len(est_input_img):
        print('error in output sizes')
        print('Frame list size:', frlistsize)
        print('Estimator input size:', len(est_input_img))
    return frame_list, est_input_img, est_input_scale
Exemplo n.º 17
0
class LMTool(QtWidgets.QMainWindow):
    def __init__(self, data=None, background=None, percentpix=0.3):
        super(LMTool, self).__init__()

        self.setupPyQtGraph()
        uic.loadUi('LMTool.ui', self)
        self.setupControls()
        self.setupTabs()
        self.setupTheory(percentpix)
        self.autonormalize = True  # FIXME: should be a UI option
        self.setupData(data, background)
        self.connectSignals()
        self.loadDefaults()

    @pyqtProperty(int)
    def maxrange(self):
        return self.bbox.value() // 2

    @pyqtProperty(list)
    def parameters(self):
        p = [
            'wavelength', 'magnification', 'n_m', 'a_p', 'n_p', 'k_p', 'x_p',
            'y_p', 'z_p', 'bbox'
        ]
        return p

    def setupPyQtGraph(self):
        pg.setConfigOption('background', 'w')
        pg.setConfigOption('foreground', 'k')
        pg.setConfigOption('imageAxisOrder', 'row-major')

    #
    # Set up widgets
    #

    def setupControls(self):
        self.bbox.checkbox.hide()
        self.x_p.setStep(1.)
        self.y_p.setStep(1.)
        self.z_p.setStep(1.)

    def setupTabs(self):
        options = dict(enableMenu=False,
                       enableMouse=False,
                       invertY=False,
                       lockAspect=True)
        self.setupImageTab(options)
        self.setupProfileTab()
        self.setupFitTab(options)

    def setupImageTab(self, options):
        self.imageTab.ci.layout.setContentsMargins(0, 0, 0, 0)
        self.image = pg.ImageItem(border=pg.mkPen('k'))
        self.imageTab.addViewBox(**options).addItem(self.image)
        pen = pg.mkPen('w', width=3)
        hoverPen = pg.mkPen('y', width=3)
        self.roi = pg.CircleROI([100, 100], radius=100., parent=self.image)
        self.roi.setPen(pen)
        self.roi.hoverPen = hoverPen
        self.roi.removeHandle(0)

    def setupProfileTab(self):
        plot = self.profilePlot
        plot.setXRange(0., self.maxrange)
        plot.showGrid(True, True, 0.2)
        plot.setLabel('bottom', 'r [pixel]')
        plot.setLabel('left', 'b(r)')
        pen = pg.mkPen('k', width=3, style=QtCore.Qt.DashLine)
        plot.addLine(y=1., pen=pen)
        pen = pg.mkPen('k', width=3)
        plot.getAxis('bottom').setPen(pen)
        plot.getAxis('left').setPen(pen)
        pen = pg.mkPen('r', width=3)
        self.theoryProfile = pg.PlotCurveItem(pen=pen)
        plot.addItem(self.theoryProfile)
        pen = pg.mkPen('k', width=3)
        self.dataProfile = pg.PlotCurveItem(pen=pen)
        plot.addItem(self.dataProfile)
        pen = pg.mkPen('k', width=1, style=QtCore.Qt.DashLine)
        self.regionUpper = pg.PlotCurveItem(pen=pen)
        self.regionLower = pg.PlotCurveItem(pen=pen)
        self.dataRegion = pg.FillBetweenItem(self.regionUpper,
                                             self.regionLower,
                                             brush=pg.mkBrush(
                                                 255, 165, 0, 128))
        plot.addItem(self.dataRegion)

    def setupFitTab(self, options):
        self.fitTab.ci.layout.setContentsMargins(0, 0, 0, 0)
        pen = pg.mkPen('k')
        self.region = pg.ImageItem(pen=pen)
        self.fit = pg.ImageItem(pen=pen)
        self.residuals = pg.ImageItem(pen=pen)
        self.fitTab.addViewBox(**options).addItem(self.region)
        self.fitTab.addViewBox(**options).addItem(self.fit)
        self.fitTab.addViewBox(**options).addItem(self.residuals)
        map = cm.get_cmap('bwr')
        map._init()
        lut = (map._lut * 255).view(np.ndarray)
        self.residuals.setLookupTable(lut)

    def setupTheory(self, percentpix):
        # Profile and Frame use the same particle and instrument
        self.particle = Sphere()
        self.instrument = Instrument()
        # Theory for radial profile
        self.theory = LMHologram(particle=self.particle,
                                 instrument=self.instrument)
        self.theory.coordinates = np.arange(self.maxrange)
        # Theory for image
        self.frame = Frame(particle=self.particle,
                           instrument=self.instrument,
                           percentpix=percentpix)

    #
    # Routines for loading data
    #
    def setupData(self, data, background):
        if type(data) is str:
            self.openHologram(data)
        else:
            self.data = data.astype(float)
        if not self.autonormalize:
            if type(background) is str:
                self.openBackground(background)
            elif type(background) is int:
                self.frame.background = background

    @pyqtSlot()
    def openHologram(self, filename=None):
        if filename is None:
            filename, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, 'Open Hologram', '', 'Images (*.png)')
        data = cv2.imread(filename, 0).astype(float)
        if data is None:
            return
        self.data = data

    @pyqtSlot()
    def openBackground(self, filename=None):
        if filename is None:
            filename, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, 'Open Background', '', 'Images (*.png)')
        background = cv2.imread(filename, 0).astype(float)
        if background is None:
            return
        self.frame.background = background

    @pyqtProperty(object)
    def data(self):
        return self.frame.data

    @data.setter
    def data(self, data):
        self.frame.data = data / np.mean(data)
        self.image.setImage(self.frame.data)
        self.x_p.setRange((0, data.shape[1] - 1))
        self.y_p.setRange((0, data.shape[0] - 1))
        self.bbox.setRange((0, min(data.shape[0] - 1, data.shape[1] - 1)))
        self.updateDataProfile()

    def loadDefaults(self):
        basedir = os.path.dirname(os.path.abspath(__file__))
        with open(os.path.join(basedir, 'LMTool.json'), 'r') as file:
            settings = json.load(file)
        for parameter in self.parameters:
            widget = getattr(self, parameter)
            for setting, value in settings[parameter].items():
                logger.debug('{}: {}: {}'.format(parameter, setting, value))
                setter_name = 'set{}'.format(setting.capitalize())
                setter = getattr(widget, setter_name)
                setter(value)

    #
    # Slots for handling user interaction
    #
    def connectSignals(self):
        self.actionOpen.triggered.connect(self.openHologram)
        self.actionSave_Parameters.triggered.connect(self.saveParameters)
        self.tabs.currentChanged.connect(self.handleTabChanged)
        for parameter in self.parameters:
            widget = getattr(self, parameter)
            widget.valueChanged['double'].connect(self.updateParameter)
        self.optimizeButton.clicked.connect(self.optimize)
        self.roi.sigRegionChanged.connect(self.handleROIChanged)

    @pyqtSlot(int)
    def handleTabChanged(self, tab):
        if (tab == 1):
            self.updateDataProfile()
        if (tab == 2):
            self.updateFit()

    @pyqtSlot(object)
    def handleROIChanged(self, roi):
        x0, y0 = roi.pos()
        dim, _ = roi.size()
        self.bbox.setValue(dim)
        self.x_p.setValue(x0 + dim / 2)
        self.y_p.setValue(y0 + dim / 2)

    @pyqtSlot(float)
    def updateParameter(self, value):
        parameter = self.sender().objectName()
        if parameter == 'bbox':
            self.profilePlot.setXRange(0., self.maxrange)
        elif hasattr(self.instrument, parameter):
            setattr(self.instrument, parameter, value)
        elif hasattr(self.particle, parameter):
            setattr(self.particle, parameter, value)
        if parameter in ['x_p', 'y_p', 'bbox']:
            self.updateROI()
        self.updatePlots()

    #
    # Routines to update plots
    #
    def updatePlots(self):
        self.updateTheoryProfile()
        self.updateDataProfile()
        if self.tabs.currentIndex() == 2:
            self.updateFit()

    def updateDataProfile(self):
        avg, std = azistd(self.frame.data, self.particle.r_p[0:2])
        self.dataProfile.setData(avg)
        self.regionUpper.setData(avg + std)
        self.regionLower.setData(avg - std)

    def updateTheoryProfile(self):
        x = np.arange(self.maxrange, dtype=float)
        y = np.full_like(x, self.particle.y_p)
        coordinates = np.stack((x + self.particle.x_p, y))
        self.theory.coordinates = coordinates
        profile = self.theory.hologram()
        self.theoryProfile.setData(x, profile)

    def updateFit(self):
        feature = self.frame.features[0]
        self.region.setImage(feature.data)
        self.fit.setImage(feature.hologram())
        self.residuals.setImage(feature.residuals())

    def updateROI(self):
        dim = self.maxrange
        x_p = self.particle.x_p
        y_p = self.particle.y_p
        h, w = self.frame.shape
        x0 = int(np.clip(x_p - dim, 0, w - 2))
        y0 = int(np.clip(y_p - dim, 0, h - 2))
        x1 = int(np.clip(x_p + dim, x0 + 1, w - 1))
        y1 = int(np.clip(y_p + dim, y0 + 1, h - 1))
        self.roi.setSize((2 * dim + 1, 2 * dim + 1), (0.5, 0.5), update=False)
        self.roi.setPos(x0, y0, update=False)
        self.frame.bboxes = [((x0, y0), x1 - x0, y1 - y0)]

    #
    # Routines associated with fitting
    #
    @pyqtSlot()
    def optimize(self):
        self.statusBar().showMessage('Optimizing parameters...')
        logger.info('Starting optimization...')
        feature = self.frame.features[0]
        feature.particle = self.particle
        if self.LMButton.isChecked():
            feature.optimizer.settings['method'] = 'lm'
            feature.optimizer.settings['loss'] = 'linear'
        else:
            feature.optimizer.settings['method'] = 'dogbox'
            feature.optimizer.settings['loss'] = 'cauchy'
        fixed = [p for p in self.parameters if getattr(self, p).fixed]
        feature.optimizer.fixed = fixed
        result = feature.optimize()
        self.updateUiValues()
        self.updatePlots()
        logger.info('Finished!\n{}'.format(str(result)))
        self.statusBar().showMessage('Optimization complete')

    def updateUiValues(self):
        '''Update Ui with parameters from particle and instrument'''
        for parameter in self.parameters:
            widget = getattr(self, parameter)
            widget.blockSignals(True)
            if hasattr(self.particle, parameter):
                widget.setValue(getattr(self.particle, parameter))
            elif hasattr(self.instrument, parameter):
                widget.setValue(getattr(self.instrument, parameter))
            widget.blockSignals(False)

    @pyqtSlot()
    def saveParameters(self, filename=None):
        if filename is None:
            filename, _ = QtWidgets.QFileDialog.getSaveFileName(
                self, 'Save Parameters', '', 'JSON (*.json)')
        parameters = {
            name: getattr(self, name).value()
            for name in self.parameters
        }
        try:
            with open(filename, 'w') as file:
                json.dump(parameters, file, indent=4, sort_keys=True)
        except IOError:
            print('error')