Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix output layer creation bug #137

Merged
merged 3 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@


class StandardizationParameters:
mean: float
std: float

def __init__(self, channels_number: int):
self.mean = np.array([0.0 for _ in range(channels_number)], dtype=np.float32)
self.std = np.array([1.0 for _ in range(channels_number)], dtype=np.float32)
Expand Down
14 changes: 11 additions & 3 deletions src/deepness/processing/map_processor/map_processing_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@
"""


from typing import Callable, Optional


class MapProcessingResult:
"""
Base class for signaling finished processing result
"""

def __init__(self, message: str):
def __init__(self, message: str, gui_delegate: Optional[Callable] = None):
"""
:param message: message to be shown to the user
:param gui_delegate: function to be called in GUI thread, as it is not safe to call GUI functions from other threads
"""
self.message = message
self.gui_delegate = gui_delegate


class MapProcessingResultSuccess(MapProcessingResult):
"""
Processing result on success
"""

def __init__(self, message: str = ''):
super().__init__(message)
def __init__(self, message: str = '', gui_delegate: Optional[Callable] = None):
super().__init__(message=message, gui_delegate=gui_delegate)


class MapProcessingResultFailed(MapProcessingResult):
Expand Down
10 changes: 7 additions & 3 deletions src/deepness/processing/map_processor/map_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ def _run(self) -> MapProcessingResult:
raise NotImplementedError('Base class not implemented!')

def finished(self, result: bool):
if not result:
if result:
gui_delegate = self._processing_result.gui_delegate
if gui_delegate is not None:
gui_delegate()
else:
self._processing_result = MapProcessingResultFailed("Unhandled processing error!")
self.finished_signal.emit(self._processing_result)

Expand Down Expand Up @@ -181,7 +185,7 @@ def _get_array_or_mmapped_array(self, final_shape_px):
shape=final_shape_px)
else:
full_result_img = np.zeros(final_shape_px, np.uint8)

return full_result_img

def tiles_generator(self) -> Tuple[np.ndarray, TileParams]:
Expand All @@ -208,7 +212,7 @@ def tiles_generator(self) -> Tuple[np.ndarray, TileParams]:

tile_img = processing_utils.get_tile_image(
rlayer=self.rlayer, extent=tile_params.extent, params=self.params)

yield tile_img, tile_params

def tiles_generator_batched(self) -> Tuple[np.ndarray, List[TileParams]]:
Expand Down
25 changes: 18 additions & 7 deletions src/deepness/processing/map_processor/map_processor_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ def _run(self) -> MapProcessingResult:
else:
all_bounding_boxes_restricted = []

self._create_vlayer_for_output_bounding_boxes(all_bounding_boxes_restricted)
gui_delegate = self._create_vlayer_for_output_bounding_boxes(all_bounding_boxes_restricted)

result_message = self._create_result_message(all_bounding_boxes_restricted)
self._all_detections = all_bounding_boxes_restricted
return MapProcessingResultSuccess(result_message)
return MapProcessingResultSuccess(
message=result_message,
gui_delegate=gui_delegate,
)

def limit_bounding_boxes_to_processed_area(self, bounding_boxes: List[Detection]) -> List[Detection]:
"""
Expand Down Expand Up @@ -112,7 +115,7 @@ def _create_result_message(self, bounding_boxes: List[Detection]) -> str:
return txt

def _create_vlayer_for_output_bounding_boxes(self, bounding_boxes: List[Detection]):
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')
vlayers = []

for channel_id in self._get_indexes_of_model_output_channels_to_create():
filtered_bounding_boxes = [det for det in bounding_boxes if det.clss == channel_id]
Expand Down Expand Up @@ -177,8 +180,16 @@ def _create_vlayer_for_output_bounding_boxes(self, bounding_boxes: List[Detectio
prov.addFeatures(features)
vlayer.updateExtents()

QgsProject.instance().addMapLayer(vlayer, False)
group.addLayer(vlayer)
vlayers.append(vlayer)

# accessing GUI from non-GUI thread is not safe, so we need to delegate it to the GUI thread
def add_to_gui():
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')
for vlayer in vlayers:
QgsProject.instance().addMapLayer(vlayer, False)
group.addLayer(vlayer)

return add_to_gui

def apply_non_maximum_suppression(self, bounding_boxes: List[Detection]) -> List[Detection]:
bboxes = []
Expand Down Expand Up @@ -221,8 +232,8 @@ def convert_bounding_boxes_to_absolute_positions(bounding_boxes_relative: List[D

def _process_tile(self, tile_img: np.ndarray, tile_params_batched: List[TileParams]) -> np.ndarray:
bounding_boxes_batched: List[Detection] = self.model.process(tile_img)

for bounding_boxes, tile_params in zip(bounding_boxes_batched, tile_params_batched):
self.convert_bounding_boxes_to_absolute_positions(bounding_boxes, tile_params)

return bounding_boxes_batched
26 changes: 16 additions & 10 deletions src/deepness/processing/map_processor/map_processor_recognition.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ def _run(self) -> MapProcessingResult:
full_result_img = full_result_img/mask
self.set_results_img(full_result_img)

self._create_rlayers_from_images_for_base_extent(self.get_result_img(), x_high, y_high, size, stride)
gui_delegate = self._create_rlayers_from_images_for_base_extent(self.get_result_img(), x_high, y_high, size, stride)
result_message = self._create_result_message(self.get_result_img(), x_high*self.params.tile_size_px, y_high*self.params.tile_size_px)
return MapProcessingResultSuccess(result_message)
return MapProcessingResultSuccess(
message=result_message,
gui_delegate=gui_delegate,
)

def _create_result_message(self, result_img: List[np.ndarray], x_high, y_high) -> str:
txt = f"Recognition ended, best result found at {x_high}, {y_high}, {result_img.shape}"
Expand Down Expand Up @@ -132,12 +135,6 @@ def _create_rlayers_from_images_for_base_extent(
size,
stride
):
group = (
QgsProject.instance()
.layerTreeRoot()
.insertGroup(0, "Cosine similarity score")
)

y = y_high * stride
x = x_high * stride

Expand All @@ -158,8 +155,17 @@ def _create_rlayers_from_images_for_base_extent(
OUTPUT_RLAYER_OPACITY = 0.5
rlayer.renderer().setOpacity(OUTPUT_RLAYER_OPACITY)

QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)
# accessing GUI from non-GUI thread is not safe, so we need to delegate it to the GUI thread
def add_to_gui():
group = (
QgsProject.instance()
.layerTreeRoot()
.insertGroup(0, "Cosine similarity score")
)
QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)

return add_to_gui

def save_result_img_as_tif(self, file_path: str, img: np.ndarray):
"""
Expand Down
24 changes: 16 additions & 8 deletions src/deepness/processing/map_processor/map_processor_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _run(self) -> MapProcessingResult:
return MapProcessingResultCanceled()

tile_results_batched = self._process_tile(tile_img_batched)

for tile_results, tile_params in zip(tile_results_batched, tile_params_batched):
for i in range(number_of_output_channels):
tile_params.set_mask_on_full_img(
Expand All @@ -52,10 +52,13 @@ def _run(self) -> MapProcessingResult:
# plt.figure(); plt.imshow(full_result_img); plt.show(block=False); plt.pause(0.001)
full_result_imgs = self.limit_extended_extent_images_to_base_extent_with_mask(full_imgs=full_result_imgs)
self.set_results_img(full_result_imgs)
self._create_rlayers_from_images_for_base_extent(self.get_result_img())

gui_delegate = self._create_rlayers_from_images_for_base_extent(self.get_result_img())
result_message = self._create_result_message(self.get_result_img())
return MapProcessingResultSuccess(result_message)
return MapProcessingResultSuccess(
message=result_message,
gui_delegate=gui_delegate,
)

def _create_result_message(self, result_imgs: List[np.ndarray]) -> str:
channels = self._get_indexes_of_model_output_channels_to_create()
Expand Down Expand Up @@ -99,11 +102,10 @@ def load_rlayer_from_file(self, file_path):
return rlayer

def _create_rlayers_from_images_for_base_extent(self, result_imgs: List[np.ndarray]):
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')

# TODO: We are creating a new file for each layer.
# Maybe can we pass ownership of this file to QGis?
# Or maybe even create vlayer directly from array, without a file?
rlayers = []

for i, channel_id in enumerate(self._get_indexes_of_model_output_channels_to_create()):
result_img = result_imgs[i]
Expand All @@ -114,9 +116,15 @@ def _create_rlayers_from_images_for_base_extent(self, result_imgs: List[np.ndarr
rlayer = self.load_rlayer_from_file(file_path)
OUTPUT_RLAYER_OPACITY = 0.5
rlayer.renderer().setOpacity(OUTPUT_RLAYER_OPACITY)
rlayers.append(rlayer)

def add_to_gui():
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')
for rlayer in rlayers:
QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)

QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)
return add_to_gui

def save_result_img_as_tif(self, file_path: str, img: np.ndarray):
"""
Expand Down
45 changes: 30 additions & 15 deletions src/deepness/processing/map_processor/map_processor_segmentation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" This file implements map processing for segmentation model """

from typing import Callable
import numpy as np
from qgis.core import QgsProject, QgsVectorLayer

Expand Down Expand Up @@ -34,9 +35,9 @@ def __init__(self,

def _run(self) -> MapProcessingResult:
final_shape_px = (self.img_size_y_pixels, self.img_size_x_pixels)

full_result_img = self._get_array_or_mmapped_array(final_shape_px)

for tile_img_batched, tile_params_batched in self.tiles_generator_batched():
if self.isCanceled():
return MapProcessingResultCanceled()
Expand All @@ -52,12 +53,16 @@ def _run(self) -> MapProcessingResult:
blur_size = int(self.segmentation_parameters.postprocessing_dilate_erode_size // 2) * 2 + 1 # needs to be odd
full_result_img = cv2.medianBlur(full_result_img, blur_size)
full_result_img = self.limit_extended_extent_image_to_base_extent_with_mask(full_img=full_result_img)

self.set_results_img(full_result_img)

self._create_vlayer_from_mask_for_base_extent(self.get_result_img())

gui_delegate = self._create_vlayer_from_mask_for_base_extent(self.get_result_img())

result_message = self._create_result_message(self.get_result_img())
return MapProcessingResultSuccess(result_message)
return MapProcessingResultSuccess(
message=result_message,
gui_delegate=gui_delegate,
)

def _create_result_message(self, result_img: np.ndarray) -> str:
unique, counts = np.unique(result_img, return_counts=True)
Expand All @@ -83,9 +88,11 @@ def _create_result_message(self, result_img: np.ndarray) -> str:

return txt

def _create_vlayer_from_mask_for_base_extent(self, mask_img):
# create vector layer with polygons from the mask image
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')
def _create_vlayer_from_mask_for_base_extent(self, mask_img) -> Callable:
""" create vector layer with polygons from the mask image
:return: function to be called in GUI thread
"""
vlayers = []

for channel_id in self._get_indexes_of_model_output_channels_to_create():
# See note in the class description why are we adding/subtracting 1 here
Expand Down Expand Up @@ -128,18 +135,26 @@ def _create_vlayer_from_mask_for_base_extent(self, mask_img):
prov.addFeatures(features)
vlayer.updateExtents()

QgsProject.instance().addMapLayer(vlayer, False)
group.addLayer(vlayer)
vlayers.append(vlayer)

# accessing GUI from non-GUI thread is not safe, so we need to delegate it to the GUI thread
def add_to_gui():
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'model_output')
for vlayer in vlayers:
QgsProject.instance().addMapLayer(vlayer, False)
group.addLayer(vlayer)

return add_to_gui

def _process_tile(self, tile_img_batched: np.ndarray) -> np.ndarray:
# TODO - create proper mapping for output channels
result = self.model.process(tile_img_batched)

result[result < self.segmentation_parameters.pixel_classification__probability_threshold] = 0.0

if (result.shape[1] == 1):
result = (result != 0).astype(int)[:, 0]
result = (result != 0).astype(int)[:, 0]
else:
result = np.argmax(result, axis=1)

return result
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _run(self) -> MapProcessingResult:
return MapProcessingResultCanceled()

tile_results_batched = self._process_tile(tile_img_batched)

for tile_results, tile_params in zip(tile_results_batched, tile_params_batched):
full_result_imgs[int(tile_params.start_pixel_y*self.superresolution_parameters.scale_factor):int((tile_params.start_pixel_y+tile_params.stride_px)*self.superresolution_parameters.scale_factor),
int(tile_params.start_pixel_x*self.superresolution_parameters.scale_factor):int((tile_params.start_pixel_x+tile_params.stride_px)*self.superresolution_parameters.scale_factor),
Expand All @@ -51,10 +51,13 @@ def _run(self) -> MapProcessingResult:
# plt.figure(); plt.imshow(full_result_img); plt.show(block=False); plt.pause(0.001)
full_result_imgs = self.limit_extended_extent_image_to_base_extent_with_mask(full_img=full_result_imgs)
self.set_results_img(full_result_imgs)
self._create_rlayers_from_images_for_base_extent(self.get_result_img())

gui_delegate = self._create_rlayers_from_images_for_base_extent(self.get_result_img())
result_message = self._create_result_message(self.get_result_img())
return MapProcessingResultSuccess(result_message)
return MapProcessingResultSuccess(
message=result_message,
gui_delegate=gui_delegate,
)

def _create_result_message(self, result_img: List[np.ndarray]) -> str:
channels = self._get_indexes_of_model_output_channels_to_create()
Expand Down Expand Up @@ -95,11 +98,10 @@ def load_rlayer_from_file(self, file_path):
return rlayer

def _create_rlayers_from_images_for_base_extent(self, result_imgs: List[np.ndarray]):
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'Super Resolution Results')

# TODO: We are creating a new file for each layer.
# Maybe can we pass ownership of this file to QGis?
# Or maybe even create vlayer directly from array, without a file?
rlayers = []

for i, channel_id in enumerate(['Super Resolution']):
result_img = result_imgs
Expand All @@ -110,9 +112,15 @@ def _create_rlayers_from_images_for_base_extent(self, result_imgs: List[np.ndarr
rlayer = self.load_rlayer_from_file(file_path)
OUTPUT_RLAYER_OPACITY = 0.5
rlayer.renderer().setOpacity(OUTPUT_RLAYER_OPACITY)
rlayers.append(rlayer)

def add_to_gui():
group = QgsProject.instance().layerTreeRoot().insertGroup(0, 'Super Resolution Results')
for rlayer in rlayers:
QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)

QgsProject.instance().addMapLayer(rlayer, False)
group.addLayer(rlayer)
return add_to_gui

def save_result_img_as_tif(self, file_path: str, img: np.ndarray):
"""
Expand Down
Loading
Loading