From 5a441838a0ac3208e5096d5ed5f7393381381e37 Mon Sep 17 00:00:00 2001 From: Robert Ohmacht Date: Tue, 26 Nov 2019 17:36:12 +0100 Subject: [PATCH] -fixes tiles not being generated if full image is only slightly bigger than a multiple of tile size and scanned images not reaching into all tiles, so full image would not be fully covered by tiles --- errors.py | 7 +++++- opticalscan.py | 27 ++++++++++++++++------ scenePyramid.py | 61 +++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/errors.py b/errors.py index c34cf7e..3dba56f 100644 --- a/errors.py +++ b/errors.py @@ -20,9 +20,14 @@ along with this program, see COPYING. If not, see . """ + class InvalidParticleError(Exception): pass class NotConnectedContoursError(Exception): - pass \ No newline at end of file + pass + + +class TileSizeError(Exception): + pass diff --git a/opticalscan.py b/opticalscan.py index 7d912ae..94a60d8 100644 --- a/opticalscan.py +++ b/opticalscan.py @@ -125,6 +125,19 @@ def removeSrcTiles(names, path): def loadAndPasteImage(srcnames, pyramid, fullzval, width, height, rotationvalue, p0, p1, p, background=None): + """ + :param list of str srcnames: list of stacked scan files to merge + :param ScenePyramid pyramid: the scene pyramid + :param numpy.ndarray fullzval: full size zval image + :param float width: width of camera + :param float height: height camera + :param float rotationvalue: angle of camera + :param list of float p0: (min x; max y) of scan tile positions + :param list of float p1: (max x; min y) of scan tile positions + :param list of float p: position of current scan tile + :param numpy.ndarray background: + :return: + """ colimgs = [] for name in srcnames: curImg = cv2imread_fix(name) @@ -680,8 +693,8 @@ class OpticalScan(QtWidgets.QWidget): self.pyramid.resetScene() self.view.blockUI() grid = np.asarray(self.dataset.grid) - p0 = [grid[:,0].min(), grid[:,1].max()] - p1 = [grid[:,0].max(), grid[:,1].min()] + p0 = [grid[:, 0].min(), grid[:, 1].max()] + p1 = [grid[:, 0].max(), grid[:, 1].min()] self.dataset.lastpos = p0 self.dataset.maxdim = p0 + p1 self.dataset.mode = "opticalscan" @@ -698,7 +711,7 @@ class OpticalScan(QtWidgets.QWidget): except queue.Empty: i = -1 - if i>=0: + if i >= 0: Ngrid = len(self.dataset.grid) names = [] @@ -721,17 +734,17 @@ class OpticalScan(QtWidgets.QWidget): removeSrcTiles(names, self.dataset.getScanPath()) self.progressbar.setValue(i+1) - if i>3: + if i > 3: timerunning = time()-self.starttime ttot = timerunning*Ngrid/(i+1) time2go = ttot - timerunning self.progresstime.setText(self.timelabeltext + str(datetime.timedelta(seconds=round(time2go)))) # reload image in sampleview, calls loadPixmap # not needed anymore as the scene gets manipulated directly via self.pyramid - #self.imageUpdate.emit(self.view.microscopeMode) + # self.imageUpdate.emit(self.view.microscopeMode) if i==Ngrid-1: - #cv2imwrite_fix(self.dataset.getImageName(), cv2.cvtColor(self.view.imgdata, cv2.COLOR_RGB2BGR)) + # cv2imwrite_fix(self.dataset.getImageName(), cv2.cvtColor(self.view.imgdata, cv2.COLOR_RGB2BGR)) self.dataset.saveZvalImg() self.process.join() self.dataqueue.close() @@ -752,4 +765,4 @@ class OpticalScan(QtWidgets.QWidget): def closeEvent(self, event): self.backGroundManager.close() - event.accept() \ No newline at end of file + event.accept() diff --git a/scenePyramid.py b/scenePyramid.py index f5957d0..825a637 100644 --- a/scenePyramid.py +++ b/scenePyramid.py @@ -26,6 +26,7 @@ import cv2 import math import copy import numpy as np +from .errors import TileSizeError from .helperfunctions import cv2imread_fix, cv2imwrite_fix from PIL import Image from PyQt5 import QtCore, QtGui, QtWidgets @@ -439,6 +440,7 @@ class ScenePyramid: for i in self.datasetParams: setattr(self, i, pyramid_params[i]) self.tileWorkingSets = copy.deepcopy(self.tileSets) + self.fixMissingTiles() self.setMicroscopeMode(self.dataset.imagescanMode) @@ -448,6 +450,7 @@ class ScenePyramid: :return: """ pyramid_params = {} + self.fixMissingTiles() for i in self.datasetParams: pyramid_params[i] = getattr(self, i) @@ -625,17 +628,26 @@ class ScenePyramid: except OSError: tile = None + # as full image size might not be divisible by tile dimensions, we have to determine correct tile size + # tile numbering is 0 based + scaling = self.scalingFactor ** slice_nr + max_x, max_y = self.tileDim + fiw = scaling * self.fullImageWidth + fih = scaling * self.fullImageHeight + + tile_w = int(math.ceil(max(min(max_x, fiw - i * max_x), 0))) + tile_h = int(math.ceil(max(min(max_y, fih - j * max_y), 0))) + + # tile does exists but its shape is not correct + if tile is not None and (tile_h, tile_w) != tile.shape[:2]: + # this should not happen + raise TileSizeError( + f"tile should have dim ({tile_w}; {tile_h}), but has dim ({tile.shape[1]}; {tile.shape[0]})" + ) + + # tile does not even exist, create if tile is None: - # as full image size might not be divisible by tile dimensions, we have to determine correct tile size - # tile numbering is 0 based - scaling = self.scalingFactor ** slice_nr - max_x, max_y = self.tileDim - fiw = scaling * self.fullImageWidth - fih = scaling * self.fullImageHeight - - x = int(math.ceil(max(min(max_x, fiw - i * max_x), 0))) - y = int(math.ceil(max(min(max_y, fih - j * max_y), 0))) - tile = np.zeros((y, x, 3), np.uint8) + tile = np.zeros((tile_h, tile_w, 3), np.uint8) return tile def createFullImage(self): @@ -860,6 +872,35 @@ class ScenePyramid: self.opacity = opacity [tile.setOpacity(opacity) for tile in self.currentTiles] + def fixMissingTiles(self): + """ + :return: + """ + scaling = 1 + tile_width, tile_height = self.tileDim + for slice in range(0, self.maxSliceNumber + 1): + img_width = math.ceil(scaling * self.fullImageWidth) + img_height = math.ceil(scaling * self.fullImageHeight) + # tiles in x direction + col_tile_count = math.ceil(img_width / tile_width) + # tiles in y direction + row_tile_count = math.ceil(img_height / tile_height) + + for i in range(0, col_tile_count): + w_tile = (tile_width if i + 1 < col_tile_count else img_width % tile_width) + if i not in self.tileSets[slice]: + self.tileSets[slice][i] = {} + self.tileWorkingSets[slice][i] = {} + col = self.tileSets[slice][i] + for j in range(0, row_tile_count): + h_tile = (tile_height if j + 1 < row_tile_count else img_height % tile_height) + if j not in col or col[j]["dimensions"] != (w_tile, h_tile): + tile = self.readViewTile(slice, i, j) + self.saveViewTile(tile, slice, i, j) + + scaling *= self.scalingFactor + + def destruct(self): """ removes all graphics items from scene -- GitLab