def flatFieldFromCalibration(bgImages, images, calcStd=False): ''' returns a flat-field correction map through conditional average of multiple images reduced by a background image calcStd -> set to True to also return the standard deviation ''' #AVERAGE BACKGROUND IMAGES IF MULTIPLE ARE GIVEN: if (type(bgImages) in (tuple, list) or type(bgImages) is np.ndarray and bgImages.ndim == 3): if len(bgImages) > 1: avgBg = averageSameExpTimes(bgImages) else: avgBg = imread(bgImages[0]) else: avgBg = imread(bgImages) i0 = imread(images[0]) - avgBg noise_level_function, _ = oneImageNLF(i0) m = MaskedMovingAverage(shape=i0.shape, calcVariance=calcStd) m.update(i0) for i in images[1:]: i = imread(i) thresh = m.avg - noise_level_function(m.avg) * 3 m.update(i, i > thresh) mx = m.avg.max() if calcStd: return m.avg / mx, m.var**0.5 / mx return m.avg / mx
def _correctDarkCurrent(self, image, exposuretime, bgImages, date): ''' open OR calculate a background image: f(t)=m*t+n ''' # either exposureTime or bgImages has to be given # if exposuretime is not None or bgImages is not None: print('... remove dark current') if bgImages is not None: if (type(bgImages) in (list, tuple) or (isinstance(bgImages, np.ndarray) and bgImages.ndim == 3)): if len(bgImages) > 1: # if multiple images are given: do STE removal: nlf = self.noise_level_function bg = SingleTimeEffectDetection( bgImages, nStd=4, noise_level_function=nlf).noSTE else: bg = imread(bgImages[0]) else: bg = imread(bgImages) else: bg = self.calcDarkCurrent(exposuretime, date) self.temp['bg'] = bg image -= bg
def __init__(self, images, noise_level_function=None, nStd=4, save_ste_indices=False, calcVariance=False, dtype=float): self.save_ste_indices = save_ste_indices i1 = imread(images[0], 'gray', dtype=dtype) i2 = imread(images[1], 'gray') self.mask_STE = None if save_ste_indices: self.mask_STE = np.zeros(shape=i1.shape, dtype=bool) self.mma = MaskedMovingAverage(shape=i1.shape, calcVariance=calcVariance, dtype=i1.dtype) # MINIMUM OF BOTH IMAGES: self.mma.update(np.min((i1, i2), axis=0)) if noise_level_function is None: noise_level_function = oneImageNLF(self.mma.avg)[0] self.noise_level_function = noise_level_function self.threshold = noise_level_function(self.mma.avg) * nStd self.addImage(np.max((i1, i2), axis=0)) for i in images[2:]: self.addImage(imread(i, 'gray'))
def flatFieldFromCalibration(bgImages, images, calcStd=False): ''' returns a flat-field correction map through conditional average of multiple images reduced by a background image calcStd -> set to True to also return the standard deviation ''' #AVERAGE BACKGROUND IMAGES IF MULTIPLE ARE GIVEN: if ( type(bgImages) in (tuple, list) or type(bgImages) is np.ndarray and bgImages.ndim == 3 ) : if len(bgImages) > 1: avgBg = averageSameExpTimes(bgImages) else: avgBg = imread(bgImages[0]) else: avgBg = imread(bgImages) i0 = imread(images[0]) - avgBg noise_level_function,_ = oneImageNLF(i0) m = MaskedMovingAverage(shape=i0.shape, calcVariance=calcStd) m.update(i0) for i in images[1:]: i = imread(i) thresh = m.avg - noise_level_function(m.avg) * 3 m.update(i, i>thresh) mx = m.avg.max() if calcStd: return m.avg/mx, m.var**0.5/mx return m.avg/mx
def _correctDarkCurrent(self, image, exposuretime, bgImages, date): ''' open OR calculate a background image: f(t)=m*t+n ''' # either exposureTime or bgImages has to be given # if exposuretime is not None or bgImages is not None: print('... remove dark current') if bgImages is not None: if (type(bgImages) in (list, tuple) or (isinstance(bgImages, np.ndarray) and bgImages.ndim == 3)): if len(bgImages) > 1: # if multiple images are given: do STE removal: nlf = self.noise_level_function bg = SingleTimeEffectDetection( bgImages, nStd=4, noise_level_function=nlf).noSTE else: bg = imread(bgImages[0]) else: bg = imread(bgImages) else: bg = self.calcDarkCurrent(exposuretime, date) self.temp['bg'] = bg image -= bg
def __init__(self, images, noise_level_function=None, nStd=4, save_ste_indices=False, calcVariance=False, dtype=float): self.save_ste_indices = save_ste_indices i1 = imread(images[0], 'gray', dtype=dtype) i2 = imread(images[1], 'gray') self.mask_STE = None if save_ste_indices: self.mask_STE = np.zeros(shape=i1.shape, dtype=bool) self.mma = MaskedMovingAverage(shape=i1.shape, calcVariance=calcVariance, dtype=i1.dtype) # MINIMUM OF BOTH IMAGES: self.mma.update(np.min((i1, i2), axis=0)) if noise_level_function is None: noise_level_function = oneImageNLF(self.mma.avg)[0] self.noise_level_function = noise_level_function self.threshold = noise_level_function(self.mma.avg) * nStd self.addImage(np.max((i1, i2), axis=0)) for i in images[2:]: self.addImage(imread(i, 'gray'))
def __init__(self, images, noise_level_function=None, nStd=4, save_ste_indices=False): self.save_ste_indices = save_ste_indices self.mask_STE = None i1 = imread(images[0], 'gray') i2 = imread(images[1], 'gray') if save_ste_indices: self.mask_STE = np.zeros(shape=i1.shape, dtype=bool) #MINIMUM OF BOTH IMAGES: self.noSTE = m = np.min((i1, i2), axis=0) if noise_level_function is None: noise_level_function = oneImageNLF(m)[0] self.noise_level_function = noise_level_function self.threshold = noise_level_function(m) * nStd self._c = 2 self.addImage(np.max((i1, i2), axis=0)) for i in images[2:]: self.addImage(imread(i, 'gray'))
def estimateFromImages(imgs1, imgs2=None, mn_mx=None, nbins=100): ''' estimate the noise level function as stDev over image intensity from a set of 2 image groups images at the same position have to show the identical setup, so imgs1[i] - imgs2[i] = noise ''' if imgs2 is None: imgs2 = [None]*len(imgs1) else: assert len(imgs1)==len(imgs2) y_vals = np.empty((len(imgs1),nbins)) w_vals = np.zeros((len(imgs1),nbins)) if mn_mx is None: print('estimating min and max image value') mn = 1e6 mx = -1e6 #get min and max image value checking all first images: for n, i1 in enumerate(imgs1): print '%s/%s' %(n+1, len(imgs1)) i1 = imread(i1) mmn, mmx = _getMinMax(i1) mn = min(mn, mmn) mx = mx = max(mx, mmx) print('--> min(%s), max(%s)' %(mn,mx)) else: mn,mx = mn_mx x = None print('get noise level function') for n,(i1, i2) in enumerate(zip(imgs1,imgs2)): print('%s/%s' %(n+1,len(imgs1))) i1 = imread(i1) if i2 is not None: i2 = imread(i2) x,y,weights, _ = calcNLF(i1, i2, mn_mx_nbins=(mn, mx, nbins), x=x) y_vals[n] = y w_vals[n] = weights #filter empty places: filledPos = np.sum(w_vals, axis=0)!=0 w_vals = w_vals[:,filledPos] y_vals = y_vals[:,filledPos] x = x[filledPos] y_avg = np.average(np.nan_to_num(y_vals), weights=w_vals, axis=0) w_vals = np.sum(w_vals, axis=0) w_vals /= w_vals.sum() fitParams, fn, i = _evaluate(x, y_avg, w_vals) return x, fn, y_avg, y_vals, w_vals, fitParams,i
def estimateFromImages(imgs1, imgs2=None, mn_mx=None, nbins=100): ''' estimate the noise level function as stDev over image intensity from a set of 2 image groups images at the same position have to show the identical setup, so imgs1[i] - imgs2[i] = noise ''' if imgs2 is None: imgs2 = [None] * len(imgs1) else: assert len(imgs1) == len(imgs2) y_vals = np.empty((len(imgs1), nbins)) w_vals = np.zeros((len(imgs1), nbins)) if mn_mx is None: print('estimating min and max image value') mn = 1e6 mx = -1e6 #get min and max image value checking all first images: for n, i1 in enumerate(imgs1): print '%s/%s' % (n + 1, len(imgs1)) i1 = imread(i1) mmn, mmx = _getMinMax(i1) mn = min(mn, mmn) mx = mx = max(mx, mmx) print('--> min(%s), max(%s)' % (mn, mx)) else: mn, mx = mn_mx x = None print('get noise level function') for n, (i1, i2) in enumerate(zip(imgs1, imgs2)): print('%s/%s' % (n + 1, len(imgs1))) i1 = imread(i1) if i2 is not None: i2 = imread(i2) x, y, weights, _ = calcNLF(i1, i2, mn_mx_nbins=(mn, mx, nbins), x=x) y_vals[n] = y w_vals[n] = weights #filter empty places: filledPos = np.sum(w_vals, axis=0) != 0 w_vals = w_vals[:, filledPos] y_vals = y_vals[:, filledPos] x = x[filledPos] y_avg = np.average(np.nan_to_num(y_vals), weights=w_vals, axis=0) w_vals = np.sum(w_vals, axis=0) w_vals /= w_vals.sum() fitParams, fn, i = _evaluate(x, y_avg, w_vals) return x, fn, y_avg, y_vals, w_vals, fitParams, i
def flatFieldFromCloseDistance2(images, bgImages=None, calcStd=False, nlf=None, nstd=6): ''' Same as [flatFieldFromCloseDistance]. Differences are: ... single-time-effect removal included ... returns the standard deviation of the image average [calcStd=True] Optional: ----------- calcStd -> set to True to also return the standard deviation nlf -> noise level function (callable) nstd -> artefact needs to deviate more than [nstd] to be removed ''' if len(images) > 1: # start with brightest images def fn(img): img = imread(img) s0, s1 = img.shape[:2] # rough approx. of image brightness: return -img[::s0 // 10, ::s1 // 10].min() images = sorted(images, key=lambda i: fn(i)) avgBg = getBackground2(bgImages, images[1]) i0 = imread(images[0], dtype=float) - avgBg i1 = imread(images[1], dtype=float) - avgBg if nlf is None: nlf = oneImageNLF(i0, i1)[0] det = SingleTimeEffectDetection( (i0, i1), nlf, nStd=nstd, calcVariance=calcStd) for i in images[1:]: i = imread(i) # exclude erroneously darker areas: thresh = det.noSTE - nlf(det.noSTE) * nstd mask = i > thresh # filter STE: det.addImage(i, mask) ma = det.noSTE else: ma = imread(images[0], dtype=float) - avgBg # fast artifact free maximum: mx = median_filter(ma[::10, ::10], 3).max() if calcStd: return ma / mx, det.mma.var**0.5 / mx return ma / mx
def getBackground(bgImages, **kwargs): # AVERAGE BACKGROUND IMAGES IF MULTIPLE ARE GIVEN: if bgImages is not None: if (type(bgImages) in (tuple, list) or isinstance(bgImages, np.ndarray) and bgImages.ndim == 3): if len(bgImages) > 1: return averageSameExpTimes(bgImages) else: return imread(bgImages[0], **kwargs) else: return imread(bgImages, **kwargs) else: return 0
def averageSameExpTimes(imgs_path): ''' average background images with same exposure time ''' firsts = imgs_path[:2] imgs = imgs_path[2:] for n, i in enumerate(firsts): firsts[n] = np.asfarray(imread(i)) d = DarkCurrentMap(firsts) for i in imgs: i = imread(i) d.addImg(i) return d.map()
def _loadImg(self): try: ID, meas, cur = self._getIDmeasCur() txt = ID.text(0), meas.text(0), cur.text(0) root = self.gui.projectFolder() p = root.join(*txt) if p == self._lastP: return self._lastP = p p0 = p.join("prev_A.jpg") p1 = p.join("prev_B.jpg") ll = len(root) + 1 if not p0.exists(): self.gui.server.download(p0[ll:], root) if not p1.exists(): self.gui.server.download(p1[ll:], root) img = imread(p0) self._grid.imageview.setImage(img, autoRange=False) img = imread(p1) self._grid.imageview2.setImage(img, autoRange=False) # load/change grid cells = ID.data(0, QtCore.Qt.UserRole)['grid'] nsublines = ID.data(0, QtCore.Qt.UserRole)['nsublines'] cdata = cur.data(0, QtCore.Qt.UserRole) # print(1111123, self._grid.grid.vertices()) # print(cdata['vertices']) vertices = cdata['vertices'] # TODO: remove different conventions # cells = cells[::-1] # nsublines = nsublines[::-1] vertices = np.array(vertices)[np.array([0, 3, 2, 1])] # print(vertices, 9898) self._grid.grid.setNCells(cells) self._grid.grid.setVertices(vertices) # print(self._grid.grid.vertices(), 888888888888888888) self._grid.edX.setValue(cells[0]) self._grid.edY.setValue(cells[1]) self._grid.edBBX.setValue(nsublines[0]) self._grid.edBBY.setValue(nsublines[1]) self._updateBtnVerified(cdata['verified']) except AttributeError as e: print(e)
def __init__(self, img): self.img = imread(img) # Fourier transform giving a complex array # with zero frequency component (DC component) will be at top left # corner self.fourier = np.fft.fft2(self.img) self.fshift = np.fft.fftshift(self.fourier)
def setReference(self, ref): ''' ref ... either quad, grid, homography or reference image quad --> list of four image points(x,y) marking the edges of the quad to correct homography --> h. matrix to correct perspective distortion referenceImage --> image of same object without perspective distortion ''' # self.maps = {} self.quad = None # self.refQuad = None self._camera_position = None self._homography = None self._homography_is_fixed = True # self.tvec, self.rvec = None, None self._pose = None # evaluate input: if isinstance(ref, np.ndarray) and ref.shape == (3, 3): # REF IS HOMOGRAPHY self._homography = ref # REF IS QUAD elif len(ref) == 4: self.quad = sortCorners(ref) # TODO: cleanup # only need to call once - here o = self.obj_points # no property any more # REF IS IMAGE else: self.ref = imread(ref) # self._refshape = ref.shape[:2] self.pattern = PatternRecognition(self.ref) self._homography_is_fixed = False
def drawChessboard(self, img=None): ''' draw a grid fitting to the last added image on this one or an extra image img == None ==False -> draw chessbord on empty image ==img ''' assert self.findCount > 0, 'cannot draw chessboard if nothing found' if img is None: img = self.img elif isinstance(img, bool) and not img: img = np.zeros(shape=(self.img.shape), dtype=self.img.dtype) else: img = imread(img, dtype='uint8') gray = False if img.ndim == 2: gray = True # need a color 8 bit image img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # Draw and display the corners cv2.drawChessboardCorners(img, self.opts['size'], self.opts['imgPoints'][-1], self.opts['foundPattern'][-1]) if gray: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) return img
def __init__(self, img): self.img = imread(img) # Fourier transform giving a complex array # with zero frequency component (DC component) will be at top left # corner self.fourier = np.fft.fft2(self.img) self.fshift = np.fft.fftshift(self.fourier)
def correctGrid(self, img, grid): ''' grid -> array of polylines=((p0x,p0y),(p1x,p1y),,,) ''' self.img = imread(img) h = self.homography #TODO: cleanup only needed to get newBorder attr. if self.opts['do_correctIntensity']: self.img = self.img / self._getTiltFactor(self.img) snew = self._newBorders warped = np.empty(snew[::-1], dtype=self.img.dtype) s0, s1 = grid.shape[:2] nx, ny = s0 - 1, s1 - 1 sy, sx = snew[0] / nx, snew[1] / ny objP = np.array([[0, 0], [sx, 0], [sx, sy], [0, sy]], dtype=np.float32) for ix in xrange(nx): for iy in xrange(ny): quad = grid[ix:ix + 2, iy:iy + 2].reshape(4, 2)[np.array([0, 2, 3, 1])] hcell = cv2.getPerspectiveTransform(quad.astype(np.float32), objP) cv2.warpPerspective(self.img, hcell, (sx, sy), warped[iy * sy:(iy + 1) * sy, ix * sx:(ix + 1) * sx], flags=cv2.INTER_LANCZOS4, **self.opts['cv2_opts']) return warped
def imgAverage(images, copy=True): ''' returns an image average works on many, also unloaded images minimises RAM usage ''' i0 = images[0] out = imread(i0, dtype='float') if copy and id(i0) == id(out): out = out.copy() for i in images[1:]: out += imread(i, dtype='float') out /= len(images) return out
def drawChessboard(self, img=None): ''' draw a grid fitting to the last added image on this one or an extra image img == None ==False -> draw chessbord on empty image ==img ''' if img is None: img = self.img elif type(img) == bool and img == False: img = np.zeros(shape=(self.img.shape), dtype=self.img.dtype) else: img = imread(img, dtype='uint8') gray = False if img.ndim == 2: gray=True #need a color 8 bit image img = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR) # Draw and display the corners cv2.drawChessboardCorners(img, self.opts['size'], self.opts['imgPoints'][-1], self.opts['foundPattern'][-1]) if gray: img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) return img
def scaleSignal(img, fitParams=None, backgroundToZero=False, reference=None): ''' scale the image between... backgroundToZero=True -> 0 (average background) and 1 (maximum signal) backgroundToZero=False -> signal+-3std reference -> reference image -- scale image to fit this one returns: scaled image ''' img = imread(img) if reference is not None: low, high = signalRange(img, fitParams) low2, high2 = signalRange(reference) img = np.asfarray(img) ampl = (high2 - low2) / (high - low) img -= low img *= ampl img += low2 return img else: offs, div = scaleParams(img, fitParams, backgroundToZero) img = np.asfarray(img) - offs img /= div print 'offset: %s, divident: %s' % (offs, div) return img
def correct(self, img): ''' ...from perspective distortion: --> perspective transformation --> apply tilt factor (view factor) correction ''' print("CORRECT PERSPECTIVE ...") self.img = imread(img) if not self._homography_is_fixed: self._homography = None h = self.homography if self.opts['do_correctIntensity']: tf = self.tiltFactor() self.img = np.asfarray(self.img) if self.img.ndim == 3: for col in range(self.img.shape[2]): self.img[..., col] /= tf else: self.img = self.img / tf warped = cv2.warpPerspective(self.img, h, self._newBorders[::-1], flags=cv2.INTER_LANCZOS4, **self.opts['cv2_opts']) return warped
def draw3dCoordAxis(self, img=None, thickness=8): ''' draw the 3d coordinate axes into given image if image == False: create an empty image ''' if img is None: img = self.img elif img is False: img = np.zeros(shape=self.img.shape, dtype=self.img.dtype) else: img = imread(img) # project 3D points to image plane: # self.opts['obj_width_mm'], self.opts['obj_height_mm'] w, h = self.opts['new_size'] axis = np.float32([[0.5 * w, 0.5 * h, 0], [w, 0.5 * h, 0], [0.5 * w, h, 0], [0.5 * w, 0.5 * h, -0.5 * w]]) t, r = self.pose() imgpts = cv2.projectPoints(axis, r, t, self.opts['cameraMatrix'], self.opts['distCoeffs'])[0] mx = int(img.max()) origin = tuple(imgpts[0].ravel()) cv2.line(img, origin, tuple(imgpts[1].ravel()), (0, 0, mx), thickness) cv2.line(img, origin, tuple(imgpts[2].ravel()), (0, mx, 0), thickness) cv2.line( img, origin, tuple(imgpts[3].ravel()), (mx, 0, 0), thickness * 2) return img
def __init__(self, img): ''' Find the Overlap between image parts and stitch them at a given edge together. There is no perspective correction. @param img: the base image ''' self.base_img_rgb = imread(img)
def scaleSignal(img, fitParams=None, backgroundToZero=False, reference=None): ''' scale the image between... backgroundToZero=True -> 0 (average background) and 1 (maximum signal) backgroundToZero=False -> signal+-3std reference -> reference image -- scale image to fit this one returns: scaled image ''' img = imread(img) if reference is not None: low, high = signalRange(img, fitParams) low2, high2 = signalRange(reference) img = np.asfarray(img) ampl = (high2-low2)/(high-low) img-=low img *= ampl img += low2 return img else: offs, div = scaleParams(img, fitParams, backgroundToZero) img = np.asfarray(img) - offs img /= div print 'offset: %s, divident: %s' %(offs, div) return img
def __init__(self, imagepath, *args, **kwargs): super().__init__() self.setWindowTitle('Set grid manual') flags = self.windowFlags() self.setWindowFlags(flags | QtCore.Qt.WindowMaximizeButtonHint) self.editor = GridEditor(*args, **kwargs) # set image img = imread(imagepath, 'gray') self.editor.imageview.setImage(img) if 'vertices' not in kwargs: # set vertices sy, sx = img.shape[:2] px, py = sx * 0.2, sy * 0.2 self.editor.grid.setVertices( np.array([[px, py], [px, sy - py], [sx - px, sy - py], [sx - px, py]])) layout = QtWidgets.QVBoxLayout() layout.addWidget(self.editor) self.setLayout(layout) btn_done = QtWidgets.QPushButton("Done") btn_done.clicked.connect(self.accept) self.editor.bottomLayout.addWidget(btn_done, 0, 0) self.values = None
def addImg(self, img, side='bottom', overlap=50, overlapDeviation=0, rotation=0, rotationDeviation=0, backgroundColor=None, params=None): ''' @param side: 'left', 'right', 'top', 'bottom', default side is 'bottom' @param overlap: overlap guess in pixels of both images @param overLapDeviation uncertainty of overlap -> overlap in range(ov-deviation, ov+deviation) @param rotation: max. rotational error between images [DEG] @param rotationDeviation: same as overLapDeviation, but for rotation [DEG] @param backgroundColor: if not None, treat this value as transparent within the stitching area ''' img_rgb = imread(img) # if iP.ARRAYS_ORDER_IS_XY: # side = {'left': 'top', # 'right': 'bottom', # 'top': 'left', # 'bottom': 'right'}[side] # the following algorithm is based on side = 'bottom', so if side in ('top', 'left'): img_rgb, self.base_img_rgb = self.base_img_rgb, img_rgb # rotate images if to be stitched 'left' or 'right' if side in ('left', 'right'): self.base_img_rgb = np.rot90(self.base_img_rgb, -1) img_rgb = np.rot90(img_rgb, -1) # check image shape assert img_rgb.shape[1] == self.base_img_rgb.shape[ 1], 'image size must be identical in stitch-direction' if params is None: # find overlap params = (offsx, offsy, rot) = self._findOverlap(img_rgb, overlap, overlapDeviation, rotation, rotationDeviation) else: offsx, offsy, rot = params img_rgb = self._rotate(img_rgb, rot) self._lastParams = params # move values in x axis: img_rgb = np.roll(img_rgb, offsx) # melt both images together self.base_img_rgb = linearBlend(self.base_img_rgb, img_rgb, offsy, backgroundColor) # rotate back if side='left' or 'right' if side in ('left', 'right'): # self.rotateImage(self.base_img_rgb,-90) self.base_img_rgb = np.rot90(self.base_img_rgb, 1) return self.base_img_rgb
def distortImage(self, image): ''' opposite of 'correct' ''' image = imread(image) (imgHeight, imgWidth) = image.shape[:2] mapx, mapy = self.getDistortRectifyMap(imgWidth, imgHeight) return cv2.remap(image, mapx, mapy, cv2.INTER_LINEAR, borderValue=(0,0,0))
def distortImage(self, image): ''' opposite of 'correct' ''' image = imread(image) (imgHeight, imgWidth) = image.shape[:2] mapx, mapy = self.getDistortRectifyMap(imgWidth, imgHeight) return cv2.remap(image, mapx, mapy, cv2.INTER_LINEAR, borderValue=(0, 0, 0))
def ff(arr): arr = imread(arr, 'gray') if arr.size > 300000: arr = arr[::10,::10] m = np.nanmean(arr) s = np.nanstd(arr) r = m-3*s,m+3*s b = (r[1]-r[0])/5 return arr, r,b
def __init__(self, img): ''' Find the Overlap between image parts and stitch them at a given edge together. There is no perspective correction. @param img: the base image @param gradient: whether to use image gradient for fitting ''' # TODO: does not work with nan in img. so far self.base_img_rgb = imread(img)
def ff(arr): arr = imread(arr, 'gray') if arr.size > 300000: arr = arr[::10, ::10] m = np.nanmean(arr) s = np.nanstd(arr) r = m - 3 * s, m + 3 * s b = (r[1] - r[0]) / 5 return arr, r, b
def imgAverage(images, copy=True): ''' returns an image average works on many, also unloaded images minimises RAM usage ''' i0 = images[0] out = imread(i0, dtype='noUint') if copy and id(i0)==id(out): out = out.copy() #moving average: c = 2 for i in images[1:]: i = imread(i, dtype='noUint') out += (i-out) / c c += 1 return out
def imgAverage(images, copy=True): ''' returns an image average works on many, also unloaded images minimises RAM usage ''' i0 = images[0] out = imread(i0, dtype='noUint') if copy and id(i0) == id(out): out = out.copy() #moving average: c = 2 for i in images[1:]: i = imread(i, dtype='noUint') out += (i - out) / c c += 1 return out
def __init__(self, img, bg=None, maxDev=1e-4, maxIter=10, remove_border_size=0, # feature_size=5, cameraMatrix=None, distortionCoeffs=None): # 20 """ Args: img (path or array): Reference image Kwargs: bg (path or array): background image - same for all given images maxDev (float): Relative deviation between the last two iteration steps Stop iterative refinement, if deviation is smaller maxIter (int): Stop iterative refinement after maxIter steps """ self.lens = None if cameraMatrix is not None: self.lens = LensDistortion() self.lens._coeffs['distortionCoeffs'] = distortionCoeffs self.lens._coeffs['cameraMatrix'] = cameraMatrix self.maxDev = maxDev self.maxIter = maxIter self.remove_border_size = remove_border_size #self.feature_size = feature_size img = imread(img, 'gray') self.bg = bg if bg is not None: self.bg = getBackground(bg) if not isinstance(self.bg, np.ndarray): self.bg = np.full_like(img, self.bg, dtype=img.dtype) else: self.bg = self.bg.astype(img.dtype) img = cv2.subtract(img, self.bg) if self.lens is not None: img = self.lens.correct(img, keepSize=True) # CREATE TEMPLATE FOR PATTERN COMPARISON: pos = self._findObject(img) self.obj_shape = img[pos].shape PatternRecognition.__init__(self, img[pos]) self._ff_mma = MaskedMovingAverage(shape=img.shape, dtype=np.float64) self.object = None self.Hs = [] # Homography matrices of all fitted images self.Hinvs = [] # same, but inverse self.fits = [] # all imaged, fitted to reference self._fit_masks = [] self._refined = False
def _correctDarkCurrent(self, image, exposuretime, bgImages, date): ''' open OR calculate a background image: f(t)=m*t+n ''' if bgImages is not None: if ( type(bgImages) in (list, tuple) or (isinstance(bgImages, np.ndarray) and bgImages.ndim==3) ): #if multiple images are given: do STE removal: bg = SingleTimeEffectDetection( (imread(bgImages[0]),imread(bgImages[1])), nStd=4).noSTE else: bg = imread(bgImages) else: d = self.coeffs['dark current'] d = _getFromDate(d, date) #calculate bg image: offs,ascent = d[2] bg = offs + ascent*exposuretime mx = self.coeffs['max value'] with np.errstate(invalid='ignore'): bg[bg>mx] = mx image-=bg
def _correctDarkCurrent(self, image, exposuretime, bgImages, date): ''' open OR calculate a background image: f(t)=m*t+n ''' if bgImages is not None: if (type(bgImages) in (list, tuple) or (isinstance(bgImages, np.ndarray) and bgImages.ndim == 3)): #if multiple images are given: do STE removal: bg = SingleTimeEffectDetection( (imread(bgImages[0]), imread(bgImages[1])), nStd=4).noSTE else: bg = imread(bgImages) else: d = self.coeffs['dark current'] d = _getFromDate(d, date) #calculate bg image: offs, ascent = d[2] bg = offs + ascent * exposuretime mx = self.coeffs['max value'] with np.errstate(invalid='ignore'): bg[bg > mx] = mx image -= bg
def __init__(self, images, noise_level_function=None, nStd=4, save_ste_indices=False): self.save_ste_indices = save_ste_indices self.mask_STE = None i1 = imread(images[0], 'gray') i2 = imread(images[1], 'gray') if save_ste_indices: self.mask_STE = np.zeros(shape=i1.shape, dtype=bool) #MINIMUM OF BOTH IMAGES: self.noSTE = m = np.min((i1,i2),axis=0) if noise_level_function is None: noise_level_function = oneImageNLF(m)[0] self.noise_level_function = noise_level_function self.threshold = noise_level_function(m)*nStd self._c = 2 self.addImage(np.max((i1,i2),axis=0)) for i in images[2:]: self.addImage(imread(i, 'gray'))
def SNR_hinken(imgs, bg=0, roi=None): ''' signal-to-noise ratio (SNR) as mean(images) / std(images) as defined in Hinken et.al. 2011 (DOI: 10.1063/1.3541766) works on unloaded images no memory overload if too many images are given ''' mean = None M = len(imgs) if bg is not 0: bg = imread(bg)[roi] if roi is not None: bg = bg[roi] #calc mean: for i in imgs: img = imread(i).asfarray() if roi is not None: img = img[roi] img -= bg if mean is None: #init mean = np.zeros_like(img) std = np.zeros_like(img) mean += img del img mean /= M #calc std of mean: for i in imgs: img = imread(i).asfarray() if roi is not None: img = img[roi] img -= bg std += (mean - img)**2 del img std = (std / M)**0.5 return mean.mean() / std.mean()
def SNR_hinken(imgs, bg=0, roi=None): ''' signal-to-noise ratio (SNR) as mean(images) / std(images) as defined in Hinken et.al. 2011 (DOI: 10.1063/1.3541766) works on unloaded images no memory overload if too many images are given ''' mean = None M = len(imgs) if bg is not 0: bg = imread(bg)[roi] if roi is not None: bg = bg[roi] #calc mean: for i in imgs: img = imread(i).asfarray() if roi is not None: img = img[roi] img -= bg if mean is None: #init mean = np.zeros_like(img) std = np.zeros_like(img) mean += img del img mean /= M #calc std of mean: for i in imgs: img = imread(i).asfarray() if roi is not None: img = img[roi] img -= bg std += (mean - img)**2 del img std = (std / M)**0.5 return mean.mean() / std.mean()
def correct(self, image, keepSize=False, borderValue=0): ''' remove lens distortion from given image ''' image = imread(image) (h, w) = image.shape[:2] mapx, mapy = self.getUndistortRectifyMap(w, h) self.img = cv2.remap(image, mapx, mapy, cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=borderValue ) if not keepSize: xx, yy, ww, hh = self.roi self.img = self.img[yy: yy + hh, xx: xx + ww] return self.img
def addImg(self, img): ''' add one chessboard image for detection lens distortion ''' # self.opts['imgs'].append(img) self.img = imread(img, 'gray', 'uint8') didFindCorners, corners = self.method() self.opts['foundPattern'].append(didFindCorners) if didFindCorners: self.findCount += 1 self.objpoints.append(self.objp) self.opts['imgPoints'].append(corners) return didFindCorners
def __init__(self, img=None, vertices=None, refinePositions=True): ''' @param img -> input image @paramn vertices -> routh estimate of corner positions @refinePositions -> whether to refine (found) corner positions ''' self.img = imread(img, 'gray') self.vertices = vertices self._pc = None if self.vertices is None: lines = self._findQuadLines() if refinePositions: lines = self._refineLines(lines) self.vertices = self._verticesFromLines(lines)
def addImg(self, img): ''' add one chessboard image for detection lens distortion ''' #self.opts['imgs'].append(img) self.img = imread(img, 'gray', 'uint8') didFindCorners, corners = self.method() self.opts['foundPattern'].append(didFindCorners) if didFindCorners: self.findCount += 1 self.objpoints.append(self.objp) self.opts['imgPoints'].append(corners) return didFindCorners
def _fitImg(self, img): ''' fit perspective and size of the input image to the reference image ''' img = imread(img, 'gray') if self.bg is not None: img = cv2.subtract(img, self.bg) if self.lens is not None: img = self.lens.correct(img, keepSize=True) (H, _, _, _, _, _, _, n_matches) = self.findHomography(img) H_inv = self.invertHomography(H) s = self.obj_shape fit = cv2.warpPerspective(img, H_inv, (s[1], s[0])) return fit, img, H, H_inv, n_matches
def addImg(self, i): img = imread(i, 'gray', dtype=float) img -= self.bg self._orig_shape = img.shape if self.scale_factor is None: # determine so that smaller image size has 50 px self.scale_factor = 100.0 / min(img.shape) s = [int(s * self.scale_factor) for s in img.shape] img = resize(img, s) if self._m is None: self._m = MaskedMovingAverage(shape=img.shape) if self.ksize is None: self.ksize = max(3, int(min(img.shape) / 10)) f = FitHistogramPeaks(img) sp = getSignalPeak(f.fitParams) # non-backround indices: ind = img > sp[1] - self.nstd * sp[2] # blur: blurred = minimum_filter(img, 3) blurred = maximum_filter(blurred, self.ksize) gblurred = gaussian_filter(blurred, self.ksize) blurred[ind] = gblurred[ind] # scale [0-1]: mn = img[~ind].mean() if np.isnan(mn): mn = 0 mx = blurred.max() blurred -= mn blurred /= (mx - mn) ind = blurred > self._m.avg self._m.update(blurred, ind) self.bglevel += mn self._mx += mx self._n += 1
def sensitivity(imgs, bg=None): ''' Extract pixel sensitivity from a set of homogeneously illuminated images This method is detailed in Section 5 of: --- K.Bedrich, M.Bokalic et al.: ELECTROLUMINESCENCE IMAGING OF PV DEVICES: ADVANCED FLAT FIELD CALIBRATION,2017 --- ''' bg = getBackground(bg) for n, i in enumerate(imgs): i = imread(i, dtype=float) i -= bg smooth = fastMean(median_filter(i, 3)) i /= smooth if n == 0: out = i else: out += i out /= (n + 1) return out
mx = mn img /= mx img = exposure.equalize_hist(img, nbins=nBins) img *= mx if intType: img = img.astype(intType) return img if __name__ == '__main__': import sys import pylab as plt import imgProcessor from imgProcessor.imgIO import imread img = imread(PathStr(imgProcessor.__file__).dirname().join( 'media', 'electroluminescence', 'EL_module_orig.PNG')) eq = equalizeImage(img.copy()) if 'no_window' not in sys.argv: plt.figure('original') plt.imshow(img) plt.figure('equalised histogram') plt.imshow(eq) plt.show()
cv2.circle(new_img, end1, r, c, thickness) cv2.circle(new_img, end2, r, c, thickness) return new_img if __name__ == '__main__': import sys from fancytools.os.PathStr import PathStr import imgProcessor from imgProcessor.imgIO import imread import pylab as plt # 1. LOAD TEST IMAGE path = PathStr(imgProcessor.__file__).dirname().join( 'media', 'electroluminescence', 'EL_cell_cracked.png') orig = imread(path) # 2. DISTORT REFERENCE IMAGE RANDOMLY: rows, cols = orig.shape # rescale r0 = 1 + 0.2 * (np.random.rand() - 1) r1 = 1 + 0.2 * (np.random.rand() - 1) dst = cv2.resize(orig, None, fx=r0, fy=r1, interpolation=cv2.INTER_CUBIC) # translate M = np.float32([[1, 0, np.random.randint(-20, 20)], [0, 1, np.random.randint(-20, 20)]]) dst = cv2.warpAffine(dst, M, (cols, rows)) # rotate: M = cv2.getRotationMatrix2D((cols / 2, rows / 2), np.random.randint(0, 90), 1) dst = cv2.warpAffine(dst, M, (cols, rows))
def __init__(self, img, # binEveryNPxVals=10, fitFunction=gaussian, bins=None, bins2=None, minNPeaks=2, maxNPeaks=4, debug=False): ''' :param binEveryNPxVals: how many intensities should be represented by one histogram bin :param fitFunction: function to fit the histogram (currently only gaussian) :param maxNPeaks: limit number of found peaks (biggest to smallest) :param debug: whether to print error messages public attributes: .fitParams -> list of fit parameters (for gaussian: (intensity, position, standard deviation)) ''' if bins is None: bins = 100 # 200 # import here to decrease startup time from scipy.optimize import curve_fit self.fitFunction = fitFunction self.fitParams = [] ind = None self.img = imread(img, 'gray') if self.img.size > 25000: # img is big enough: dot need to analyse full img self.img = self.img[::10, ::10] try: self.yvals, bin_edges = np.histogram(self.img, bins=bins) except: ind = np.isfinite(self.img) self.yvals, bin_edges = np.histogram(self.img[ind], bins=bins) self.yvals = self.yvals.astype(np.float32) # move histogram range to representative area: cdf = np.cumsum(self.yvals) / self.yvals.sum() i0 = np.argmax(cdf > 0.01) i1 = np.argmax(cdf > 0.99) mnImg = bin_edges[i0] mxImg = bin_edges[i1] if bins2 is None: bins2 = 50 if self.img.dtype.kind != 'f' or abs(mxImg - mnImg) > 10: binEveryNPxVals = 5 # one bin for every N pixelvalues bins2 = np.clip(int((mxImg - mnImg) / binEveryNPxVals), 25, 100) if ind is not None: img = self.img[ind] else: img = self.img self.yvals, bin_edges = np.histogram(img, bins=bins2, range=(mnImg, mxImg)) # bin edges give start and end of an area-> move that to the middle: self.xvals = bin_edges[:-1] + np.diff(bin_edges) * 0.5 # in the (quite unlikely) event of two yvals being identical in sequence # peak detection wont work there, so remove these vals before: valid = np.append( np.logical_and(self.yvals[:-1] != 0, np.diff(self.yvals) != 0), True) self.yvals = self.yvals[valid] self.xvals = self.xvals[valid] yvals = self.yvals.copy() xvals = self.xvals s0, s1 = self.img.shape minY = max(10, float(s0 * s1) / bins2 / 50) mindist = int(5 * 100 / bins2) peaks = self._findPeaks(yvals, mindist, maxNPeaks, minY) valleys = self._findValleys(yvals, peaks) positions = self._sortPositions(peaks, valleys) d = minNPeaks - len(positions) if d > 0: positions.extend([(0, None, -1)] * d) # FIT FUNCTION TO EACH PEAK: for il, i, ir in positions: # peak position/value: if i is None: i = np.argmax(yvals) xp = xvals[i] yp = yvals[i] xcut = xvals[il:ir] ycut = yvals[il:ir] # approximate standard deviation from FHWM: #ymean = 0.5* (yp + ycut[-1]) #sigma = abs(xp - findXAt(xcut,ycut,ymean) ) sigma = 0.5 * abs(xvals[ir] - xvals[il]) init_guess = (yp, xp, sigma) # FIT try: # fitting procedure using initial guess params, _ = curve_fit(self.fitFunction, xcut, ycut, p0=init_guess, sigma=np.ones(shape=xcut.shape) * 1e-8) except (RuntimeError, TypeError): # TypeError: not enough values given (when peaks and valleys to # close to each other) if debug: print( "couln't fit gaussians -> result will will inaccurate") # stay with initial guess: params = init_guess # except TypeError, err: # print err # #couldn't fit maybe because to less values were given # continue if (params[0] > 0): # has height # and #has height # peak is within the image histogram params = list(params) params[2] = np.abs(params[2]) self.fitParams.append(params) y = self.fitFunction(self.xvals, *params).astype(yvals.dtype) yvals -= y # peaks add up yvals[yvals < 0] = 0 # can't be negative # sort for increasing x positions self.fitParams = sorted(self.fitParams, key=lambda p: p[1])