Source code for arthropod_describer.common.scale_setting_widget
import copy
import dataclasses
import logging
import math
import re
import typing
from pathlib import Path
import PySide2
import cv2
import numpy as np
import qimage2ndarray
from PySide2 import QtGui, QtCore
from PySide2.QtCore import Qt, Signal, QPoint, QObject, QRect
from PySide2.QtGui import QImage, QPixmap
from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QSpinBox, QPushButton, QComboBox, QDoubleSpinBox, QDialog, \
QDialogButtonBox, QVBoxLayout, QStyleOptionViewItem
from arthropod_describer.common.blocking_operation import BlockingOperation
from arthropod_describer.common.image_operation_binding import ImageOperation
from arthropod_describer.common.photo import Photo, UpdateContext
from arthropod_describer.common.photo_layer import PhotoLayer
from arthropod_describer.common.state import State
from arthropod_describer.common.storage import Storage
from arthropod_describer.common.units import SIPrefix, Unit, BaseUnit, Value, UnitStore
from arthropod_describer.common.utils import ScaleSetting, ScaleLineInfo, get_scale_marker_roi, get_reference_length, \
get_scale_line_ends
from arthropod_describer.common.visualization_layer import VisualizationLayer
from arthropod_describer.image_viewer import ImageViewer
from arthropod_describer.thumbnail_storage import ThumbnailDelegate, ThumbnailStorage_
from arthropod_describer.tools.ruler import Tool_Ruler
[docs]@dataclasses.dataclass
class ScaleExtractionResult:
photo_idx: int
image_scale: Value
reference_length: Value
px_length: Value
scale_line_p1: typing.Tuple[int, int]
scale_line_p2: typing.Tuple[int, int]
scale_marker: typing.Optional[np.ndarray]
scale_marker_bbox: typing.Optional[typing.Tuple[int, int, int, int]]
[docs]@dataclasses.dataclass
class ScaleSettingTuple:
old_scale_set: ScaleSetting
new_scale_set: ScaleSetting
[docs]class ScaleSettingWidget(QWidget):
scale_set = Signal(list)
accepted = Signal()
cancelled = Signal()
def __init__(self, state: State, parent: typing.Optional[PySide2.QtWidgets.QWidget] = None,
f: PySide2.QtCore.Qt.WindowFlags = Qt.WindowFlags()):
super().__init__(parent, f)
self.state = state
self._state = self.state
self.photo: typing.Optional[Photo] = None
self._storage: typing.Optional[Storage] = None
self.units = UnitStore()
# self.setLayout(self._layout)
# self.line_length: Value = Value(0, self.state.units.units['px'])
# self.ref_length: Value = Value(0, self._cmbUnits.itemData(self._cmbUnits.currentIndex()))
# self.scale: Value = Value(0, self.state.units.units['px'] / self.ref_length.unit)
self._setup_controls()
self._setup_viewer()
self.image_op: ImageOperation = ImageOperation(self)
self.scale_settings: typing.Dict[Path, ScaleSettingTuple] = {}
self.setWindowTitle('Set scale')
self.setWindowModality(Qt.ApplicationModal)
def _setup_controls(self):
self._layout = QHBoxLayout()
self._ref_label = QLabel("Reference length: ")
self._spboxReference_mm = QSpinBox()
# self._spboxReference_mm.setSuffix(' mm')
self._spboxReference_mm.setMinimum(1)
self._spboxReference_mm.setMaximum(9999)
self._spboxReference_mm.setMaximumWidth(150)
self._spboxReference_mm.setAlignment(Qt.AlignLeft)
self._spboxReference_mm.valueChanged.connect(self._new_reference_length)
self._cmbUnits = QComboBox()
self._cmbResUnits = QComboBox()
default_idx = 0
for i, prefix in enumerate(list(SIPrefix)):
unit = Unit(BaseUnit.m, prefix=prefix, dim=1)
self._cmbUnits.addItem(str(unit), unit)
self._cmbResUnits.addItem(str(unit), unit)
if prefix == SIPrefix.m:
default_idx = i
self._cmbUnits.setCurrentIndex(default_idx)
self._cmbUnits.currentIndexChanged.connect(self._handle_reference_unit_changed)
self._lblPixels = QLabel('Line length: 0 px')
self._lblResolution = QLabel('Scale:')
self._lblScaleValue = QLabel()
# self._spboxResolution.setRange(0, 9999)
# self._spboxResolution.setSuffix(f' {self.units.units["px/mm"]}')
# self._spboxResolution.setSpecialValueText('not set')
self._btnExtractScales = QPushButton(text="Extract scale from selected")
self._btnExtractScales.clicked.connect(self._extract_scales)
self._btnSetManually = QPushButton(text="Enter scale numerically")
self._btnSetManually.clicked.connect(self._get_scale_from_user)
self._btnAccept = QPushButton(text="Accept")
self._btnAccept.clicked.connect(self.accepted.emit)
self._btnAccept.setEnabled(True)
self._btnResolutionGlobal = QPushButton()
self._btnResolutionGlobal.setText("Set globally")
self._btnResolutionGlobal.clicked.connect(self._set_global_resolution)
self._btnResolutionGlobal.setEnabled(True)
self._btnCancel = QPushButton(text="Cancel")
self._btnCancel.clicked.connect(self.cancelled.emit)
self._btnCancel.setEnabled(True)
self._layout.addWidget(self._ref_label)
self._layout.addWidget(self._spboxReference_mm)
self._layout.addWidget(self._cmbUnits)
self._layout.addWidget(self._lblPixels)
self._layout.addWidget(self._lblResolution)
self._layout.addWidget(self._lblScaleValue)
self._layout.addWidget(self._btnExtractScales)
self._layout.addWidget(self._btnSetManually)
self._layout.addWidget(self._btnAccept)
self._layout.addWidget(self._btnResolutionGlobal)
self._layout.addWidget(self._btnCancel)
def _setup_viewer(self):
self.image_viewer = ImageViewer(self.state)
self.image_viewer.first_photo_requested.connect(self._fetch_first_photo)
self.image_viewer.prev_photo_requested.connect(self._fetch_prev_photo)
self.image_viewer.next_photo_requested.connect(self._fetch_next_photo)
self.image_viewer.last_photo_requested.connect(self._fetch_last_photo)
self.image_viewer.ui.tbtnRotateCW.hide()
self.image_viewer.ui.tbtnRotateCCW.hide()
#self.image_viewer.rotate_cw_requested.connect(self._rotate_cw)
#self.image_viewer.rotate_ccw_requested.connect(self._rotate_ccw)
self.image_viewer.rotate_cw_requested.connect(lambda: self.image_op.rotate(clockwise=True))
self.image_viewer.rotate_ccw_requested.connect(lambda: self.image_op.rotate(clockwise=False))
self.image_viewer.photo_view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.image_viewer.photo_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.image_viewer.photo_switched.connect(self.set_photo)
self.image_viewer.add_layer(PhotoLayer(self.state))
self.viz_layer = VisualizationLayer()
self.viz_layer.initialize()
self.viz_layer.setOpacity(.90)
self.image_viewer.add_layer(self.viz_layer)
self.image_viewer.hide()
# self.ruler_tool: typing.Optional[Tool_Ruler] = None # Tool_Ruler(self._state)
self.ruler_tool: Tool_Ruler = Tool_Ruler(self._state)
self.ruler_tool.show_real_units(False)
self.ruler_tool.current_value.connect(self.compute_resolution)
self.ruler_tools: typing.List[Tool_Ruler] = []
self.image_viewer.set_tool(self.ruler_tool)
self.image_viewer.ui.controlBar.addItem(self._layout)
self.setLayout(self.image_viewer.layout())
def _fetch_photo(self, idx):
idx = min(max(idx, 0), self._storage.image_count - 1)
# TODO: Is there any difference between self.temp_storage and self._state.storage?
# _fetch_photo() now serves as a single image fetching function, but some
# of the functions it replaced seemed to use self.temp_storage, and some
# seemed to use self._state.storage in some places.
# if self.temp_storage == self._state.storage:
# print('self.temp_storage == self._state.storage')
# else:
# print('!!! self.temp_storage != self._state.storage')
# self.image_viewer.enable_navigation_buttons(idx) # TODO: or should this work with temp_storage? (in that case, pass the appropriate storage as a parameter of enable_navigation_buttons()?)
photo = self._storage.get_photo_by_idx(idx)
scale_set = self.scale_settings[photo.image_path]
self.setWindowTitle(f'{photo.image_name} - Set scale')
# self.image_viewer.setWindowTitle(f"Please delineate scale line for {photo.image_name}")
self.image_viewer.set_photo(photo, reset_view=True, reset_tool=False)
# self.ruler_tool = self.ruler_tools[idx]
# value_mm: Optional[int] = None
# dig_re = re.compile(r'([0-9]+)\s*([a-zA-Z]m)')
# if self.ruler_tool._measurement <= 0:
# scale_marker, (left, top, width, height) = get_scale_marker_roi(photo.image)
# p1x, p1y, p2x, p2y = get_scale_line_ends(scale_marker)
# scale_text, scale_roi = get_reference_length(scale_marker)
# print(scale_text)
# if len(scale_text) > 0:
# match = dig_re.match(scale_text)
# for group in match.groups():
# print(group)
# print(scale_text)
# self.ruler_tool.set_line(QPoint(p1x+left, p1y+top), QPoint(p2x+left, p2y+top), reset_others=True)
# self.set_photo(photo)
# res = self.temp_storage.image_scales[idx]
# res = self.temp_storage.get_photo_by_idx(idx).image_scale
# if res is not None and res.value > 0:
# self._lblResolution.setText(f'Scale: {res}')
# else:
# self._lblResolution.setText('Scale: n/a')
#
# if self.ruler_tool.value_storage is not None and len(self.ruler_tool.value_storage) > 0:
# self._lblPixels.setText(f'Line length: {self.ruler_tool.value_storage[0]} px')
# else:
# self._lblPixels.setText('Line length: 0 px')
# self._scale_set_widget.set_photo(photo)
# if self.ruler_tool.value_storage is not None and len(self.ruler_tool.value_storage) > 0:
# # self._scale_set_widget.set_line_length(Value(self.ruler_tool.value_storage[0], self._state.units.units['px']))
# self.set_line_length(self.ruler_tool.value_storage[0])
# else:
# self.set_line_length(Value(0, self._state.units.units['px']))
def _fetch_first_photo(self):
self._fetch_photo(0)
def _fetch_prev_photo(self):
self._fetch_photo(self._state.storage.image_names.index(self._state.current_photo.image_name) - 1) # TODO: or should this work with temp_storage?
def _fetch_next_photo(self):
self._fetch_photo(self._state.storage.image_names.index(self._state.current_photo.image_name) + 1) # TODO: or should this work with temp_storage?
def _fetch_last_photo(self):
self._fetch_photo(self._state.storage.image_count - 1) # TODO: or should this work with temp_storage?
def _new_reference_length(self, value: int):
self.compute_resolution(self.scale_settings[self.photo.image_path].new_scale_set.scale_line.length)
def _set_global_resolution(self):
idx = self.state.storage.image_names.index(self.state.current_photo.image_name)
photo_ = self.state.storage.get_photo_by_idx(idx, load_image=False)
# for i in range(self.state.storage.image_count):
# photo = self.state.storage.get_photo_by_idx(i, load_image=False)
# photo.image_scale = photo_.image_scale
scale_setting = self.scale_settings[photo_.image_path].new_scale_set
for img_path, scale_tup in self.scale_settings.items():
if img_path == photo_.image_path:
continue
scale_tup.new_scale_set.scale = scale_setting.scale
scale_tup.new_scale_set.scale_line = None #ScaleLineInfo((0, 0), (0, 0), Value(0, self.ruler_tool._px_unit))
scale_tup.new_scale_set.reference_length = None
# self.scale_set.emit([i for i in range(self.state.storage.image_count)])
self.accepted.emit()
[docs] def compute_resolution(self, pixels: typing.Optional[Value]):
if pixels is None:
self.unset_line()
return
self.line_length = pixels
self._lblPixels.setText(f'Line length: {pixels}')
if pixels.value > 0:
self._cmbUnits.setEnabled(True)
self._spboxReference_mm.setEnabled(True)
else:
self.unset_line()
return
idx = self.state.storage.image_names.index(self.state.current_photo.image_name)
photo = self.state.storage.get_photo_by_idx(idx, load_image=False)
ref_length = Value(self._spboxReference_mm.value(), self._cmbUnits.itemData(self._cmbUnits.currentIndex()))
res = pixels / ref_length
# if photo.image_scale is None:
# unit = self.state.units.units['px'] / self._cmbUnits.itemData(self._cmbUnits.currentIndex())
# photo.image_scale = Value(value=res, unit=unit)
self.scale = res
scale_setting = self.scale_settings[photo.image_path]
scale_setting.new_scale_set.scale = res
if pixels.value == 0:
scale_setting.new_scale_set.scale = None
if scale_setting.new_scale_set.scale_line is None:
if len(self.ruler_tool.endpoints) >= 2:
scale_setting.new_scale_set.scale_line = ScaleLineInfo(
p1=self.ruler_tool.endpoints[0].toTuple(),
p2=self.ruler_tool.endpoints[1].toTuple(),
length=pixels
)
else:
scale_setting.new_scale_set.scale_line = ScaleLineInfo(
p1=(0, 0),
p2=(0, 0),
length=Value(0, self.ruler_tool._px_unit)
)
scale_setting.new_scale_set.scale_line.length = pixels
scale_setting.new_scale_set.reference_length = ref_length
self.ruler_tool.set_scale(res)
if len(self.ruler_tool.endpoints) > 1:
scale_setting.new_scale_set.scale_line.p1 = self.ruler_tool.endpoints[0].toTuple()
scale_setting.new_scale_set.scale_line.p2 = self.ruler_tool.endpoints[1].toTuple()
# photo.image_scale = res
# photo.image_scale.unit = self.state.units.units['px'] / self._cmbUnits.itemData(self._cmbUnits.currentIndex())
# self._lblResolution.setText(f'Scale: {scale_setting.new_scale_set.scale}')
self._lblScaleValue.setText(str(scale_setting.new_scale_set.scale))
# self._lblScaleValue.setSuffix(f' {res.unit}')
self._btnAccept.setEnabled(True)
self._btnResolutionGlobal.setEnabled(True)
self.set_line(scale_setting.new_scale_set.scale_line, scale_setting.new_scale_set.reference_length)
# self.scale_set.emit([idx])
[docs] def set_photo(self, photo: Photo):
self.photo = photo
self.image_op.init(photo)
self._state.current_photo = photo
res = photo.scale_setting.scale
scale_setting = self.scale_settings[photo.image_path].new_scale_set
self.ruler_tool.set_scale(scale_setting.scale)
scale_line = scale_setting.scale_line
if scale_line is not None:
# self.ruler_tool.set_line(QPoint(*scale_line.p1),
# QPoint(*scale_line.p2),
# reset_others=True)
# self.set_line_length(scale_line.length)
# self._spboxReference_mm.setEnabled(True)
# self._cmbUnits.setEnabled(True)
# if (ref_length := scale_setting.reference_length) is not None:
# self._spboxReference_mm.setValue(ref_length.value)
# self._cmbUnits.setCurrentIndex(list(SIPrefix).index(ref_length.unit.prefix))
self.set_line(scale_line, scale_setting.reference_length)
else:
self.unset_line()
# self.ruler_tool.reset_tool()
# self._lblPixels.setText('Line length: no line drawn')
# # if there is not scale line yet, disable referene length spinner and units combobox
# # self.set_line_length(Value(0, self.state.units.units['px']))
# self._spboxReference_mm.setEnabled(False)
# self._cmbUnits.setEnabled(False)
if res is not None and res.value > 0:
# self._lblResolution.setText(f'Scale: {scale_setting.scale}')
self._lblScaleValue.setText(str(scale_setting.scale))
# self._lblScaleValue.setSuffix(f' {scale_setting.scale.unit}')
# idx = list(SIPrefix).index(scale_setting.reference_length.unit.prefix)
# self._cmbUnits.setCurrentIndex(idx)
# ref_length = scale_setting.reference_length
# self._spboxReference_mm.setValue(ref_length.value)
else:
# self._lblResolution.setText('Scale')
self._lblScaleValue.setText('not set')
self.setWindowTitle(f'{photo.image_name} - Set scale')
self.image_viewer.set_tool(self.ruler_tool, reset_current=False)
self.viz_layer.paint_commands = self.ruler_tool.viz_commands
self.viz_layer.update()
[docs] def set_line_length(self, length: Value):
self.line_length = length
if length.value > 0:
self._cmbUnits.setEnabled(True)
self._spboxReference_mm.setEnabled(True)
self._lblPixels.setText(f'Line length: {self.line_length}')
if self.photo.scale_setting.reference_length is not None:
self._spboxReference_mm.setValue(self.photo.scale_setting.reference_length.value)
else:
if length.value > 0 and self.photo.image_scale is not None and self.photo.image_scale.value > 0:
self._spboxReference_mm.setValue(round(length.value / self.photo.image_scale.value))
self.compute_resolution(self.line_length)
def _handle_reference_unit_changed(self, idx: int):
# self._lblScaleValue.setSuffix(f' px / {self._cmbUnits.itemData(idx)}')
self.compute_resolution(self.line_length)
self._lblScaleValue.setText(str(self.scale_settings[self.photo.image_path].new_scale_set.scale))
[docs] def initialize(self, storage: Storage):
logging.info(f'Scale setting: initializing storage {storage.location}')
self.image_viewer.set_storage(storage)
if self._storage is not None:
self._storage.update_photo.disconnect(self._handle_update_photo)
self._storage = storage
self._storage.update_photo.connect(self._handle_update_photo)
self._storage.storage_update.connect(self._handle_storage_update)
self.scale_settings.clear()
self.ruler_tool.reset_tool()
for idx in range(self._storage.image_count):
photo = self._storage.get_photo_by_idx(idx, load_image=False)
self.scale_settings[photo.image_path] = ScaleSettingTuple(
old_scale_set=photo.scale_setting,
new_scale_set=copy.deepcopy(photo.scale_setting)
)
def _handle_update_photo(self, img_name: str, ctx: UpdateContext, data):
if self.photo is None or img_name != self.photo.image_name:
return
photo = self._storage.get_photo_by_name(img_name)
scale_setting = self.scale_settings[photo.image_path].new_scale_set
self.ruler_tool.set_scale(scale_setting.scale)
scale_line = scale_setting.scale_line
if (ref_length := scale_setting.reference_length) is not None:
self._spboxReference_mm.setValue(ref_length.value)
self._cmbUnits.setCurrentIndex(list(SIPrefix).index(ref_length.unit.prefix))
if scale_line is not None and 'operation' in data:
if data['operation'].startswith('rot'):
scale_line.rotate(data['operation'] == 'rot_90_ccw', (round(0.5 * photo.image_size[1]),
round(0.5 * photo.image_size[0])))
elif data['operation'] == 'resize':
f = data['factor']
# scale_line.p1 = (round(f * scale_line.p1[0]),
# round(f * scale_line.p1[1]))
# scale_line.p2 = (round(f * scale_line.p2[0]),
# round(f * scale_line.p2[1]))
# scale_line.scale(f, (round(0.5 * photo.image_size[0]),
# round(0.5 * photo.image_size[1])))
pass
# self.ruler_tool.set_line(QPoint(*scale_line.p1),
# QPoint(*scale_line.p2),
# reset_others=True)
# self.set_line_length(scale_line.length)
self.set_line(scale_line, scale_setting.reference_length)
else:
# self.set_line_length(Value(0, self.state.units.units['px']))
self.unset_line()
self.viz_layer.paint_commands = self.ruler_tool.viz_commands
self.viz_layer.update()
def _handle_storage_update(self, data: typing.Dict[str, typing.Any]):
if 'photos' not in data:
return
if 'included' in data['photos']:
for photo_name in data['photos']['included']:
photo = self._storage.get_photo_by_name(photo_name, load_image=False)
self.scale_settings[photo.image_path] = ScaleSettingTuple(
old_scale_set=photo.scale_setting,
new_scale_set=copy.deepcopy(photo.scale_setting)
)
def _set_unit_to_cmbUnits(self, unit: Unit):
self._cmbUnits.setCurrentIndex(list(SIPrefix).index(unit.prefix))
[docs] def set_line(self, scale_line: ScaleLineInfo, ref_length: Value):
scale_setting = self.scale_settings[self.photo.image_path].new_scale_set
self._spboxReference_mm.setEnabled(True)
self._spboxReference_mm.blockSignals(True)
self._spboxReference_mm.setValue(ref_length.value)
self._spboxReference_mm.blockSignals(False)
self._cmbUnits.setEnabled(True)
self._set_unit_to_cmbUnits(ref_length.unit)
self.ruler_tool.set_line(QPoint(*scale_line.p1), QPoint(*scale_line.p2), reset_others=True)
# self.set_line_length(scale_line.length)
# self._lblScaleValue.setValue((scale_line.length / ref_length).value)
self._lblScaleValue.setText(str(scale_line.length / ref_length))
self._lblPixels.setText(f'Line length: {scale_line.length}')
self.viz_layer.paint_commands = self.ruler_tool.viz_commands
self.viz_layer.update()
[docs] def unset_line(self):
self._spboxReference_mm.setEnabled(False)
self._cmbUnits.setEnabled(False)
self._lblPixels.setText('Line length: no line drawn')
self._lblScaleValue.setText('not set')
self.ruler_tool.reset_tool()
def _get_scale_from_user(self):
dialog = QDialog()
dialog.setWindowTitle("Enter scale numerically")
spbox = QDoubleSpinBox()
spbox.setRange(0, 999)
spbox.setSpecialValueText('0 (invalid value)')
spbox.setValue(self.scale_settings[self.photo.image_path].new_scale_set.scale.value)
cmbUnits = QComboBox()
scale_setting = self.scale_settings[self.photo.image_path].new_scale_set
default_idx = 0
for i, prefix in enumerate(list(SIPrefix)):
unit = self.units.units['px'] / Unit(BaseUnit.m, prefix=prefix, dim=1)
cmbUnits.addItem(str(unit), unit)
if scale_setting.scale is not None and scale_setting.scale.unit == unit:
default_idx = i
cmbUnits.setCurrentIndex(default_idx)
diag_buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
diag_buttons.accepted.connect(dialog.accept)
diag_buttons.rejected.connect(dialog.reject)
vbox = QVBoxLayout()
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Scale: '))
hbox.addWidget(spbox)
hbox.addWidget(cmbUnits)
vbox.addLayout(hbox)
vbox.addWidget(diag_buttons)
dialog.setLayout(vbox)
if dialog.exec_() == QDialog.Accepted:
scale_setting.scale = Value(spbox.value(), cmbUnits.currentData())
scale_setting.scale_line = None
scale_setting.reference_length = None
self.unset_line()
self._lblScaleValue.setText(str(scale_setting.scale))
def _extract_scales(self):
block_op = BlockingOperation(self.state.storage, list(range(self.state.storage.image_count)),
operation=self._scale_extraction_operation,
result_handler=self._scale_extraction_result_processing,
parent=self)
block_op.start()
def _scale_extraction_operation(self, storage: Storage, idx: int) -> typing.Optional[ScaleExtractionResult]:
dig_re = re.compile(r'([0-9]+)\s*([a-zA-Z]m)')
unit_store = UnitStore()
img_path = storage.image_paths[idx]
image = cv2.imread(str(img_path))
scale_marker, (left, top, width, height) = get_scale_marker_roi(image)
ref_length, scale_rotated = get_reference_length(scale_marker)
p1x, p1y, p2x, p2y = get_scale_line_ends(scale_marker)
if p1x < 0:
return None
if len(ref_length) > 0:
match = dig_re.match(ref_length)
length = int(match.groups()[0])
unit_str = match.groups()[1]
unit = unit_store.units[unit_str]
ref_length = Value(length, unit)
px_length = Value(round(math.sqrt(((p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y)))),
unit_store.units['px'])
image_scale = px_length / ref_length
else:
ref_length = None
image_scale = None
px_length = None
p1x_, p1y_, p2x_, p2y_ = get_scale_line_ends(scale_rotated)
scale_rotated = cv2.cvtColor(scale_rotated, cv2.COLOR_GRAY2RGB)
scale_rotated = cv2.line(scale_rotated, (p1x_, p1y_ - 10), (p1x_, p1y_ + 10), [0, 255, 0], thickness=2)
scale_rotated = cv2.line(scale_rotated, (p2x_, p1y_ - 10), (p2x_, p1y_ + 10), [0, 255, 0], thickness=2)
scale_rotated = cv2.resize(scale_rotated, (154, 26), interpolation=cv2.INTER_LINEAR)
# pixmap = QPixmap.fromImage(qimage2ndarray.array2qimage(scale_rotated))
scale_marker = scale_rotated
return ScaleExtractionResult(idx, image_scale, ref_length, px_length,
(p1x+left, p1y+top),
(p2x+left, p2y+top),
scale_marker,
(left, top, width, height))
def _scale_extraction_result_processing(self, storage: Storage, idx: int, result: typing.Optional[ScaleExtractionResult]):
if result is None:
return
path = storage.image_paths[idx]
sc_info = self.scale_settings[path].new_scale_set
sc_info.scale = result.image_scale
if sc_info.scale_line is None:
sc_info.scale_line = ScaleLineInfo(p1=(-1, -1), p2=(-1, -1), length=Value(0, self.units.units['px']))
sc_info.reference_length = result.reference_length
sc_info.scale_line.p1 = result.scale_line_p1
sc_info.scale_line.p2 = result.scale_line_p2
sc_info.scale_line.length.value = np.sqrt(np.sum(np.square(np.array(sc_info.scale_line.p1) - np.array(sc_info.scale_line.p2))))
sc_info.scale_marker_bbox = result.scale_marker_bbox
sc_info.scale_marker_img = QPixmap.fromImage(qimage2ndarray.array2qimage(result.scale_marker))
if path == self.state.current_photo.image_path:
self.set_line(sc_info.scale_line, sc_info.reference_length)
self._btnAccept.setEnabled(True)
[docs] def closeEvent(self, event:PySide2.QtGui.QCloseEvent):
super().closeEvent(event)
self.cancelled.emit()
[docs]class ScaleItemDelegate(ThumbnailDelegate):
def __init__(self, thumbnails: ThumbnailStorage_, scale_set_widget: ScaleSettingWidget,
parent: QObject = None):
super().__init__(thumbnails, parent)
self.scale_set_widget = scale_set_widget
[docs] def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
super().paint(painter, option, index)
path = index.data(Qt.UserRole + 8)
scale_marker: typing.Optional[QPixmap] = self.scale_set_widget.scale_settings[path].new_scale_set.scale_marker_img # TODO replace 7 with a named constant
thumbnail: QImage = index.data(Qt.UserRole + 3)
rect: QRect = option.rect
if scale_marker is None:
return
scale_rect = QRect(rect.center().x() - 0.5 * scale_marker.width(), rect.bottom() - 32 - scale_marker.height(),
scale_marker.width(), scale_marker.height())
painter.save()
painter.setRenderHint(painter.SmoothPixmapTransform, True)
# painter.drawImage(scale_rect, scale_marker)
painter.drawPixmap(scale_rect, scale_marker)
painter.restore()