diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4aabec0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM ubuntu:18.04 +# COPY requirements.txt /opt/app/requirements.txt +# WORKDIR /opt/app +# RUN pip3 install -r requirements.txt +# COPY . /opt/app +# CMD ["python", "/opt/app/main.py"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..95ec0f1 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Visualyse + +## Installation + +Use `pip3 install -r requirements.txt` to install the necessary libraries for the project. +Use `python3 main.py` to start the program \ No newline at end of file diff --git a/exe_generator/arabic_reshaper/__init__.py b/exe_generator/arabic_reshaper/__init__.py new file mode 100644 index 0000000..43cbf1c --- /dev/null +++ b/exe_generator/arabic_reshaper/__init__.py @@ -0,0 +1,12 @@ +import os + +from .arabic_reshaper import reshape, default_reshaper, ArabicReshaper +from .reshaper_config import (config_for_true_type_font, + ENABLE_NO_LIGATURES, + ENABLE_SENTENCES_LIGATURES, + ENABLE_WORDS_LIGATURES, + ENABLE_LETTERS_LIGATURES, + ENABLE_ALL_LIGATURES) + + +exec(open(os.path.join(os.path.dirname(__file__), '__version__.py')).read()) diff --git a/exe_generator/arabic_reshaper/__version__.py b/exe_generator/arabic_reshaper/__version__.py new file mode 100644 index 0000000..a33997d --- /dev/null +++ b/exe_generator/arabic_reshaper/__version__.py @@ -0,0 +1 @@ +__version__ = '2.1.0' diff --git a/exe_generator/arabic_reshaper/arabic_reshaper.py b/exe_generator/arabic_reshaper/arabic_reshaper.py new file mode 100644 index 0000000..4721a6a --- /dev/null +++ b/exe_generator/arabic_reshaper/arabic_reshaper.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- + +# This work is licensed under the MIT License. +# To view a copy of this license, visit https://opensource.org/licenses/MIT + +# Written by Abdullah Diab (mpcabd) +# Email: mpcabd@gmail.com +# Website: http://mpcabd.xyz + +from __future__ import unicode_literals + +import re + +from itertools import repeat + +from .ligatures import LIGATURES +from .reshaper_config import auto_config +from .letters import (UNSHAPED, ISOLATED, TATWEEL, ZWJ, LETTERS_ARABIC, + LETTERS_ARABIC_V2, LETTERS_KURDISH, FINAL, + INITIAL, MEDIAL, connects_with_letters_before_and_after, + connects_with_letter_before, connects_with_letter_after) + +HARAKAT_RE = re.compile( + '[' + '\u0610-\u061a' + '\u064b-\u065f' + '\u0670' + '\u06d6-\u06dc' + '\u06df-\u06e8' + '\u06ea-\u06ed' + '\u08d4-\u08e1' + '\u08d4-\u08ed' + '\u08e3-\u08ff' + ']', + + re.UNICODE | re.X +) + + +class ArabicReshaper(object): + """ + A class for Arabic reshaper, it allows for fine-tune configuration over the + API. + + If no configuration is passed to the constructor, the class will check for + an environment variable :envvar:`PYTHON_ARABIC_RESHAPER_CONFIGURATION_FILE` + , if the variable is available, the class will load the file pointed to by + the variable, and will read it as an ini file. + If the variable doesn't exist, the class will load with the default + configuration file :file:`default-config.ini` + + Check these links for information on the configuration files format: + + * Python 3: https://docs.python.org/3/library/configparser.html + * Python 2: https://docs.python.org/2/library/configparser.html + + See the default configuration file :file:`default-config.ini` for details + on how to configure your reshaper. + """ + + def __init__(self, configuration=None, configuration_file=None): + super(ArabicReshaper, self).__init__() + + self.configuration = auto_config(configuration, configuration_file) + self.language = self.configuration.get('language') + + if self.language == 'ArabicV2': + self.letters = LETTERS_ARABIC_V2 + elif self.language == 'Kurdish': + self.letters = LETTERS_KURDISH + else: + self.letters = LETTERS_ARABIC + + @property + def _ligatures_re(self): + if not hasattr(self, '__ligatures_re'): + patterns = [] + re_group_index_to_ligature_forms = {} + index = 0 + FORMS = 1 + MATCH = 0 + for ligature_record in LIGATURES: + ligature, replacement = ligature_record + if not self.configuration.getboolean(ligature): + continue + re_group_index_to_ligature_forms[index] = replacement[FORMS] + patterns.append('({})'.format(replacement[MATCH])) + index += 1 + self._re_group_index_to_ligature_forms = ( + re_group_index_to_ligature_forms + ) + self.__ligatures_re = re.compile('|'.join(patterns), re.UNICODE) + return self.__ligatures_re + + def _get_ligature_forms_from_re_group_index(self, group_index): + if not hasattr(self, '_re_group_index_to_ligature_forms'): + return self._ligatures_re + return self._re_group_index_to_ligature_forms[group_index] + + def reshape(self, text): + if not text: + return '' + + output = [] + + LETTER = 0 + FORM = 1 + NOT_SUPPORTED = -1 + + delete_harakat = self.configuration.getboolean('delete_harakat') + delete_tatweel = self.configuration.getboolean('delete_tatweel') + support_zwj = self.configuration.getboolean('support_zwj') + shift_harakat_position = self.configuration.getboolean( + 'shift_harakat_position' + ) + use_unshaped_instead_of_isolated = self.configuration.getboolean( + 'use_unshaped_instead_of_isolated' + ) + + positions_harakat = {} + + isolated_form = (UNSHAPED + if use_unshaped_instead_of_isolated else ISOLATED) + + for letter in text: + if HARAKAT_RE.match(letter): + if not delete_harakat: + position = len(output) - 1 + if shift_harakat_position: + position -= 1 + if position not in positions_harakat: + positions_harakat[position] = [] + if shift_harakat_position: + positions_harakat[position].insert(0, letter) + else: + positions_harakat[position].append(letter) + elif letter == TATWEEL and delete_tatweel: + pass + elif letter == ZWJ and not support_zwj: + pass + elif letter not in self.letters: + output.append((letter, NOT_SUPPORTED)) + elif not output: # first letter + output.append((letter, isolated_form)) + else: + previous_letter = output[-1] + if previous_letter[FORM] == NOT_SUPPORTED: + output.append((letter, isolated_form)) + elif not connects_with_letter_before(letter, self.letters): + output.append((letter, isolated_form)) + elif not connects_with_letter_after( + previous_letter[LETTER], self.letters): + output.append((letter, isolated_form)) + elif (previous_letter[FORM] == FINAL and not + connects_with_letters_before_and_after( + previous_letter[LETTER], self.letters + )): + output.append((letter, isolated_form)) + elif previous_letter[FORM] == isolated_form: + output[-1] = ( + previous_letter[LETTER], + INITIAL + ) + output.append((letter, FINAL)) + # Otherwise, we will change the previous letter to connect + # to the current letter + else: + output[-1] = ( + previous_letter[LETTER], + MEDIAL + ) + output.append((letter, FINAL)) + + # Remove ZWJ if it's the second to last item as it won't be useful + if support_zwj and len(output) > 1 and output[-2][LETTER] == ZWJ: + output.pop(len(output) - 2) + + if support_zwj and output and output[-1][LETTER] == ZWJ: + output.pop() + + if self.configuration.getboolean('support_ligatures'): + # Clean text from Harakat to be able to find ligatures + text = HARAKAT_RE.sub('', text) + + # Clean text from Tatweel to find ligatures if delete_tatweel + if delete_tatweel: + text = text.replace(TATWEEL, '') + + for match in re.finditer(self._ligatures_re, text): + group_index = next(( + i for i, group in enumerate(match.groups()) if group + ), -1) + forms = self._get_ligature_forms_from_re_group_index( + group_index + ) + a, b = match.span() + a_form = output[a][FORM] + b_form = output[b - 1][FORM] + ligature_form = None + + # +-----------+----------+---------+---------+----------+ + # | a \ b | ISOLATED | INITIAL | MEDIAL | FINAL | + # +-----------+----------+---------+---------+----------+ + # | ISOLATED | ISOLATED | INITIAL | INITIAL | ISOLATED | + # | INITIAL | ISOLATED | INITIAL | INITIAL | ISOLATED | + # | MEDIAL | FINAL | MEDIAL | MEDIAL | FINAL | + # | FINAL | FINAL | MEDIAL | MEDIAL | FINAL | + # +-----------+----------+---------+---------+----------+ + + if a_form in (isolated_form, INITIAL): + if b_form in (isolated_form, FINAL): + ligature_form = ISOLATED + else: + ligature_form = INITIAL + else: + if b_form in (isolated_form, FINAL): + ligature_form = FINAL + else: + ligature_form = MEDIAL + if not forms[ligature_form]: + continue + output[a] = (forms[ligature_form], NOT_SUPPORTED) + output[a+1:b] = repeat(('', NOT_SUPPORTED), b - 1 - a) + + result = [] + if not delete_harakat and -1 in positions_harakat: + result.extend(positions_harakat[-1]) + for i, o in enumerate(output): + if o[LETTER]: + if o[FORM] == NOT_SUPPORTED or o[FORM] == UNSHAPED: + result.append(o[LETTER]) + else: + result.append(self.letters[o[LETTER]][o[FORM]]) + + if not delete_harakat: + if i in positions_harakat: + result.extend(positions_harakat[i]) + + return ''.join(result) + + +default_reshaper = ArabicReshaper() +reshape = default_reshaper.reshape diff --git a/exe_generator/arabic_reshaper/default-config.ini b/exe_generator/arabic_reshaper/default-config.ini new file mode 100644 index 0000000..6a675c7 --- /dev/null +++ b/exe_generator/arabic_reshaper/default-config.ini @@ -0,0 +1,328 @@ +[ArabicReshaper] +# Supported languages are: [Arabic, ArabicV2, Kurdish] +# More languages might be supported soon. +# `Arabic` is default and recommended to work in most of the cases and supports (Arabic, Urdu and Farsi) +# `ArabicV2` is only to be used with certain font that you run into missing chars +# `Kurdish` if you are using Kurdish Sarchia font is recommended, work with both unicode and classic Arabic-Kurdish keybouard +language = Arabic + +# Whether to delete the Harakat (Tashkeel) before reshaping or not. +delete_harakat = yes + +# Whether to shift the Harakat (Tashkeel) one position so they appear correctly when string is reversed +shift_harakat_position = no + +# Whether to delete the Tatweel (U+0640) before reshaping or not. +delete_tatweel = no + +# Whether to support ZWJ (U+200D) or not. +support_zwj = yes + +# Use unshaped form instead of isolated form. +use_unshaped_instead_of_isolated = no + +# Whether to use ligatures or not. +# Serves as a shortcut to disable all ligatures. +support_ligatures = yes + +# When `support_ligatures` is enabled. +# Separate ligatures configuration take precedence over it. +# When `support_ligatures` is disabled, +# separate ligatures configurations are ignored. + +# --------------------- Begin: Ligatures Configurations -------------------- # + +# Sentences (Enabled on top) +ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM = no +ARABIC LIGATURE JALLAJALALOUHOU = no +ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM = no + +# Words (Enabled on top) +ARABIC LIGATURE ALLAH = yes +ARABIC LIGATURE AKBAR = no +ARABIC LIGATURE ALAYHE = no +ARABIC LIGATURE MOHAMMAD = no +ARABIC LIGATURE RASOUL = no +ARABIC LIGATURE SALAM = no +ARABIC LIGATURE SALLA = no +ARABIC LIGATURE WASALLAM = no +RIAL SIGN = no + +# Letters (Enabled on top) +ARABIC LIGATURE LAM WITH ALEF = yes +ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE = yes +ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW = yes +ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE = yes +ARABIC LIGATURE AIN WITH ALEF MAKSURA = no +ARABIC LIGATURE AIN WITH JEEM = no +ARABIC LIGATURE AIN WITH JEEM WITH MEEM = no +ARABIC LIGATURE AIN WITH MEEM = no +ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE AIN WITH MEEM WITH MEEM = no +ARABIC LIGATURE AIN WITH MEEM WITH YEH = no +ARABIC LIGATURE AIN WITH YEH = no +ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF = no +ARABIC LIGATURE ALEF WITH FATHATAN = no +ARABIC LIGATURE BEH WITH ALEF MAKSURA = no +ARABIC LIGATURE BEH WITH HAH = no +ARABIC LIGATURE BEH WITH HAH WITH YEH = no +ARABIC LIGATURE BEH WITH HEH = no +ARABIC LIGATURE BEH WITH JEEM = no +ARABIC LIGATURE BEH WITH KHAH = no +ARABIC LIGATURE BEH WITH KHAH WITH YEH = no +ARABIC LIGATURE BEH WITH MEEM = no +ARABIC LIGATURE BEH WITH NOON = no +ARABIC LIGATURE BEH WITH REH = no +ARABIC LIGATURE BEH WITH YEH = no +ARABIC LIGATURE BEH WITH ZAIN = no +ARABIC LIGATURE DAD WITH ALEF MAKSURA = no +ARABIC LIGATURE DAD WITH HAH = no +ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA = no +ARABIC LIGATURE DAD WITH HAH WITH YEH = no +ARABIC LIGATURE DAD WITH JEEM = no +ARABIC LIGATURE DAD WITH KHAH = no +ARABIC LIGATURE DAD WITH KHAH WITH MEEM = no +ARABIC LIGATURE DAD WITH MEEM = no +ARABIC LIGATURE DAD WITH REH = no +ARABIC LIGATURE DAD WITH YEH = no +ARABIC LIGATURE FEH WITH ALEF MAKSURA = no +ARABIC LIGATURE FEH WITH HAH = no +ARABIC LIGATURE FEH WITH JEEM = no +ARABIC LIGATURE FEH WITH KHAH = no +ARABIC LIGATURE FEH WITH KHAH WITH MEEM = no +ARABIC LIGATURE FEH WITH MEEM = no +ARABIC LIGATURE FEH WITH MEEM WITH YEH = no +ARABIC LIGATURE FEH WITH YEH = no +ARABIC LIGATURE GHAIN WITH ALEF MAKSURA = no +ARABIC LIGATURE GHAIN WITH JEEM = no +ARABIC LIGATURE GHAIN WITH MEEM = no +ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM = no +ARABIC LIGATURE GHAIN WITH MEEM WITH YEH = no +ARABIC LIGATURE GHAIN WITH YEH = no +ARABIC LIGATURE HAH WITH ALEF MAKSURA = no +ARABIC LIGATURE HAH WITH JEEM = no +ARABIC LIGATURE HAH WITH JEEM WITH YEH = no +ARABIC LIGATURE HAH WITH MEEM = no +ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE HAH WITH MEEM WITH YEH = no +ARABIC LIGATURE HAH WITH YEH = no +ARABIC LIGATURE HEH WITH ALEF MAKSURA = no +ARABIC LIGATURE HEH WITH JEEM = no +ARABIC LIGATURE HEH WITH MEEM = no +ARABIC LIGATURE HEH WITH MEEM WITH JEEM = no +ARABIC LIGATURE HEH WITH MEEM WITH MEEM = no +ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF = no +ARABIC LIGATURE HEH WITH YEH = no +ARABIC LIGATURE JEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE JEEM WITH HAH = no +ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA = no +ARABIC LIGATURE JEEM WITH HAH WITH YEH = no +ARABIC LIGATURE JEEM WITH MEEM = no +ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE JEEM WITH MEEM WITH HAH = no +ARABIC LIGATURE JEEM WITH MEEM WITH YEH = no +ARABIC LIGATURE JEEM WITH YEH = no +ARABIC LIGATURE KAF WITH ALEF = no +ARABIC LIGATURE KAF WITH ALEF MAKSURA = no +ARABIC LIGATURE KAF WITH HAH = no +ARABIC LIGATURE KAF WITH JEEM = no +ARABIC LIGATURE KAF WITH KHAH = no +ARABIC LIGATURE KAF WITH LAM = no +ARABIC LIGATURE KAF WITH MEEM = no +ARABIC LIGATURE KAF WITH MEEM WITH MEEM = no +ARABIC LIGATURE KAF WITH MEEM WITH YEH = no +ARABIC LIGATURE KAF WITH YEH = no +ARABIC LIGATURE KHAH WITH ALEF MAKSURA = no +ARABIC LIGATURE KHAH WITH HAH = no +ARABIC LIGATURE KHAH WITH JEEM = no +ARABIC LIGATURE KHAH WITH MEEM = no +ARABIC LIGATURE KHAH WITH YEH = no +ARABIC LIGATURE LAM WITH ALEF MAKSURA = no +ARABIC LIGATURE LAM WITH HAH = no +ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA = no +ARABIC LIGATURE LAM WITH HAH WITH MEEM = no +ARABIC LIGATURE LAM WITH HAH WITH YEH = no +ARABIC LIGATURE LAM WITH HEH = no +ARABIC LIGATURE LAM WITH JEEM = no +ARABIC LIGATURE LAM WITH JEEM WITH JEEM = no +ARABIC LIGATURE LAM WITH JEEM WITH MEEM = no +ARABIC LIGATURE LAM WITH JEEM WITH YEH = no +ARABIC LIGATURE LAM WITH KHAH = no +ARABIC LIGATURE LAM WITH KHAH WITH MEEM = no +ARABIC LIGATURE LAM WITH MEEM = no +ARABIC LIGATURE LAM WITH MEEM WITH HAH = no +ARABIC LIGATURE LAM WITH MEEM WITH YEH = no +ARABIC LIGATURE LAM WITH YEH = no +ARABIC LIGATURE MEEM WITH ALEF = no +ARABIC LIGATURE MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE MEEM WITH HAH = no +ARABIC LIGATURE MEEM WITH HAH WITH JEEM = no +ARABIC LIGATURE MEEM WITH HAH WITH MEEM = no +ARABIC LIGATURE MEEM WITH HAH WITH YEH = no +ARABIC LIGATURE MEEM WITH JEEM = no +ARABIC LIGATURE MEEM WITH JEEM WITH HAH = no +ARABIC LIGATURE MEEM WITH JEEM WITH KHAH = no +ARABIC LIGATURE MEEM WITH JEEM WITH MEEM = no +ARABIC LIGATURE MEEM WITH JEEM WITH YEH = no +ARABIC LIGATURE MEEM WITH KHAH = no +ARABIC LIGATURE MEEM WITH KHAH WITH JEEM = no +ARABIC LIGATURE MEEM WITH KHAH WITH MEEM = no +ARABIC LIGATURE MEEM WITH KHAH WITH YEH = no +ARABIC LIGATURE MEEM WITH MEEM = no +ARABIC LIGATURE MEEM WITH MEEM WITH YEH = no +ARABIC LIGATURE MEEM WITH YEH = no +ARABIC LIGATURE NOON WITH ALEF MAKSURA = no +ARABIC LIGATURE NOON WITH HAH = no +ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA = no +ARABIC LIGATURE NOON WITH HAH WITH MEEM = no +ARABIC LIGATURE NOON WITH HAH WITH YEH = no +ARABIC LIGATURE NOON WITH HEH = no +ARABIC LIGATURE NOON WITH JEEM = no +ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE NOON WITH JEEM WITH HAH = no +ARABIC LIGATURE NOON WITH JEEM WITH MEEM = no +ARABIC LIGATURE NOON WITH JEEM WITH YEH = no +ARABIC LIGATURE NOON WITH KHAH = no +ARABIC LIGATURE NOON WITH MEEM = no +ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE NOON WITH MEEM WITH YEH = no +ARABIC LIGATURE NOON WITH NOON = no +ARABIC LIGATURE NOON WITH REH = no +ARABIC LIGATURE NOON WITH YEH = no +ARABIC LIGATURE NOON WITH ZAIN = no +ARABIC LIGATURE QAF WITH ALEF MAKSURA = no +ARABIC LIGATURE QAF WITH HAH = no +ARABIC LIGATURE QAF WITH MEEM = no +ARABIC LIGATURE QAF WITH MEEM WITH HAH = no +ARABIC LIGATURE QAF WITH MEEM WITH MEEM = no +ARABIC LIGATURE QAF WITH MEEM WITH YEH = no +ARABIC LIGATURE QAF WITH YEH = no +ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN = no +ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF = no +ARABIC LIGATURE SAD WITH ALEF MAKSURA = no +ARABIC LIGATURE SAD WITH HAH = no +ARABIC LIGATURE SAD WITH HAH WITH HAH = no +ARABIC LIGATURE SAD WITH HAH WITH YEH = no +ARABIC LIGATURE SAD WITH KHAH = no +ARABIC LIGATURE SAD WITH MEEM = no +ARABIC LIGATURE SAD WITH MEEM WITH MEEM = no +ARABIC LIGATURE SAD WITH REH = no +ARABIC LIGATURE SAD WITH YEH = no +ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN = no +ARABIC LIGATURE SEEN WITH ALEF MAKSURA = no +ARABIC LIGATURE SEEN WITH HAH = no +ARABIC LIGATURE SEEN WITH HAH WITH JEEM = no +ARABIC LIGATURE SEEN WITH HEH = no +ARABIC LIGATURE SEEN WITH JEEM = no +ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE SEEN WITH JEEM WITH HAH = no +ARABIC LIGATURE SEEN WITH KHAH = no +ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA = no +ARABIC LIGATURE SEEN WITH KHAH WITH YEH = no +ARABIC LIGATURE SEEN WITH MEEM = no +ARABIC LIGATURE SEEN WITH MEEM WITH HAH = no +ARABIC LIGATURE SEEN WITH MEEM WITH JEEM = no +ARABIC LIGATURE SEEN WITH MEEM WITH MEEM = no +ARABIC LIGATURE SEEN WITH REH = no +ARABIC LIGATURE SEEN WITH YEH = no +ARABIC LIGATURE SHADDA WITH DAMMA = no +ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM = no +ARABIC LIGATURE SHADDA WITH DAMMA MEDIAL FORM = no +ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM = no +ARABIC LIGATURE SHADDA WITH FATHA = no +ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM = no +ARABIC LIGATURE SHADDA WITH FATHA MEDIAL FORM = no +ARABIC LIGATURE SHADDA WITH KASRA = no +ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM = no +ARABIC LIGATURE SHADDA WITH KASRA MEDIAL FORM = no +ARABIC LIGATURE SHADDA WITH KASRATAN ISOLATED FORM = no +ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF = no +ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF ISOLATED FORM = no +ARABIC LIGATURE SHEEN WITH ALEF MAKSURA = no +ARABIC LIGATURE SHEEN WITH HAH = no +ARABIC LIGATURE SHEEN WITH HAH WITH MEEM = no +ARABIC LIGATURE SHEEN WITH HAH WITH YEH = no +ARABIC LIGATURE SHEEN WITH HEH = no +ARABIC LIGATURE SHEEN WITH JEEM = no +ARABIC LIGATURE SHEEN WITH JEEM WITH YEH = no +ARABIC LIGATURE SHEEN WITH KHAH = no +ARABIC LIGATURE SHEEN WITH MEEM = no +ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH = no +ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM = no +ARABIC LIGATURE SHEEN WITH REH = no +ARABIC LIGATURE SHEEN WITH YEH = no +ARABIC LIGATURE TAH WITH ALEF MAKSURA = no +ARABIC LIGATURE TAH WITH HAH = no +ARABIC LIGATURE TAH WITH MEEM = no +ARABIC LIGATURE TAH WITH MEEM WITH HAH = no +ARABIC LIGATURE TAH WITH MEEM WITH MEEM = no +ARABIC LIGATURE TAH WITH MEEM WITH YEH = no +ARABIC LIGATURE TAH WITH YEH = no +ARABIC LIGATURE TEH WITH ALEF MAKSURA = no +ARABIC LIGATURE TEH WITH HAH = no +ARABIC LIGATURE TEH WITH HAH WITH JEEM = no +ARABIC LIGATURE TEH WITH HAH WITH MEEM = no +ARABIC LIGATURE TEH WITH HEH = no +ARABIC LIGATURE TEH WITH JEEM = no +ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE TEH WITH JEEM WITH MEEM = no +ARABIC LIGATURE TEH WITH JEEM WITH YEH = no +ARABIC LIGATURE TEH WITH KHAH = no +ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA = no +ARABIC LIGATURE TEH WITH KHAH WITH MEEM = no +ARABIC LIGATURE TEH WITH KHAH WITH YEH = no +ARABIC LIGATURE TEH WITH MEEM = no +ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA = no +ARABIC LIGATURE TEH WITH MEEM WITH HAH = no +ARABIC LIGATURE TEH WITH MEEM WITH JEEM = no +ARABIC LIGATURE TEH WITH MEEM WITH KHAH = no +ARABIC LIGATURE TEH WITH MEEM WITH YEH = no +ARABIC LIGATURE TEH WITH NOON = no +ARABIC LIGATURE TEH WITH REH = no +ARABIC LIGATURE TEH WITH YEH = no +ARABIC LIGATURE TEH WITH ZAIN = no +ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF = no +ARABIC LIGATURE THEH WITH ALEF MAKSURA = no +ARABIC LIGATURE THEH WITH HEH = no +ARABIC LIGATURE THEH WITH JEEM = no +ARABIC LIGATURE THEH WITH MEEM = no +ARABIC LIGATURE THEH WITH NOON = no +ARABIC LIGATURE THEH WITH REH = no +ARABIC LIGATURE THEH WITH YEH = no +ARABIC LIGATURE THEH WITH ZAIN = no +ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA = no +ARABIC LIGATURE YEH WITH ALEF MAKSURA = no +ARABIC LIGATURE YEH WITH HAH = no +ARABIC LIGATURE YEH WITH HAH WITH YEH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU = no +ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN = no +ARABIC LIGATURE YEH WITH HEH = no +ARABIC LIGATURE YEH WITH JEEM = no +ARABIC LIGATURE YEH WITH JEEM WITH YEH = no +ARABIC LIGATURE YEH WITH KHAH = no +ARABIC LIGATURE YEH WITH MEEM = no +ARABIC LIGATURE YEH WITH MEEM WITH MEEM = no +ARABIC LIGATURE YEH WITH MEEM WITH YEH = no +ARABIC LIGATURE YEH WITH NOON = no +ARABIC LIGATURE YEH WITH REH = no +ARABIC LIGATURE YEH WITH YEH = no +ARABIC LIGATURE YEH WITH ZAIN = no +ARABIC LIGATURE ZAH WITH MEEM = no + +# ---------------------- End: Ligatures Configurations --------------------- # diff --git a/exe_generator/arabic_reshaper/letters.py b/exe_generator/arabic_reshaper/letters.py new file mode 100644 index 0000000..e0ebd71 --- /dev/null +++ b/exe_generator/arabic_reshaper/letters.py @@ -0,0 +1,529 @@ +# Each letter is of the format: +# +# ('', ) +# +# And replacement is of the format: +# +# ('', '', '', '') +# +# Where is the string to replace, and is the replacement in +# case should be in isolated form, is the replacement in +# case should be in initial form, is the replacement in case +# should be in medial form, and is the replacement in case +# should be in final form. If no replacement is specified for a form, +# then no that means the letter doesn't support this form. + +from __future__ import unicode_literals + +UNSHAPED = 255 +ISOLATED = 0 +INITIAL = 1 +MEDIAL = 2 +FINAL = 3 + +TATWEEL = '\u0640' +ZWJ = '\u200D' +LETTERS_ARABIC = { + # ARABIC LETTER HAMZA + '\u0621': ('\uFE80', '', '', ''), + # ARABIC LETTER ALEF WITH MADDA ABOVE + '\u0622': ('\uFE81', '', '', '\uFE82'), + # ARABIC LETTER ALEF WITH HAMZA ABOVE + '\u0623': ('\uFE83', '', '', '\uFE84'), + # ARABIC LETTER WAW WITH HAMZA ABOVE + '\u0624': ('\uFE85', '', '', '\uFE86'), + # ARABIC LETTER ALEF WITH HAMZA BELOW + '\u0625': ('\uFE87', '', '', '\uFE88'), + # ARABIC LETTER YEH WITH HAMZA ABOVE + '\u0626': ('\uFE89', '\uFE8B', '\uFE8C', '\uFE8A'), + # ARABIC LETTER ALEF + '\u0627': ('\uFE8D', '', '', '\uFE8E'), + # ARABIC LETTER BEH + '\u0628': ('\uFE8F', '\uFE91', '\uFE92', '\uFE90'), + # ARABIC LETTER TEH MARBUTA + '\u0629': ('\uFE93', '', '', '\uFE94'), + # ARABIC LETTER TEH + '\u062A': ('\uFE95', '\uFE97', '\uFE98', '\uFE96'), + # ARABIC LETTER THEH + '\u062B': ('\uFE99', '\uFE9B', '\uFE9C', '\uFE9A'), + # ARABIC LETTER JEEM + '\u062C': ('\uFE9D', '\uFE9F', '\uFEA0', '\uFE9E'), + # ARABIC LETTER HAH + '\u062D': ('\uFEA1', '\uFEA3', '\uFEA4', '\uFEA2'), + # ARABIC LETTER KHAH + '\u062E': ('\uFEA5', '\uFEA7', '\uFEA8', '\uFEA6'), + # ARABIC LETTER DAL + '\u062F': ('\uFEA9', '', '', '\uFEAA'), + # ARABIC LETTER THAL + '\u0630': ('\uFEAB', '', '', '\uFEAC'), + # ARABIC LETTER REH + '\u0631': ('\uFEAD', '', '', '\uFEAE'), + # ARABIC LETTER ZAIN + '\u0632': ('\uFEAF', '', '', '\uFEB0'), + # ARABIC LETTER SEEN + '\u0633': ('\uFEB1', '\uFEB3', '\uFEB4', '\uFEB2'), + # ARABIC LETTER SHEEN + '\u0634': ('\uFEB5', '\uFEB7', '\uFEB8', '\uFEB6'), + # ARABIC LETTER SAD + '\u0635': ('\uFEB9', '\uFEBB', '\uFEBC', '\uFEBA'), + # ARABIC LETTER DAD + '\u0636': ('\uFEBD', '\uFEBF', '\uFEC0', '\uFEBE'), + # ARABIC LETTER TAH + '\u0637': ('\uFEC1', '\uFEC3', '\uFEC4', '\uFEC2'), + # ARABIC LETTER ZAH + '\u0638': ('\uFEC5', '\uFEC7', '\uFEC8', '\uFEC6'), + # ARABIC LETTER AIN + '\u0639': ('\uFEC9', '\uFECB', '\uFECC', '\uFECA'), + # ARABIC LETTER GHAIN + '\u063A': ('\uFECD', '\uFECF', '\uFED0', '\uFECE'), + # ARABIC TATWEEL + TATWEEL: (TATWEEL, TATWEEL, TATWEEL, TATWEEL), + # ARABIC LETTER FEH + '\u0641': ('\uFED1', '\uFED3', '\uFED4', '\uFED2'), + # ARABIC LETTER QAF + '\u0642': ('\uFED5', '\uFED7', '\uFED8', '\uFED6'), + # ARABIC LETTER KAF + '\u0643': ('\uFED9', '\uFEDB', '\uFEDC', '\uFEDA'), + # ARABIC LETTER LAM + '\u0644': ('\uFEDD', '\uFEDF', '\uFEE0', '\uFEDE'), + # ARABIC LETTER MEEM + '\u0645': ('\uFEE1', '\uFEE3', '\uFEE4', '\uFEE2'), + # ARABIC LETTER NOON + '\u0646': ('\uFEE5', '\uFEE7', '\uFEE8', '\uFEE6'), + # ARABIC LETTER HEH + '\u0647': ('\uFEE9', '\uFEEB', '\uFEEC', '\uFEEA'), + # ARABIC LETTER WAW + '\u0648': ('\uFEED', '', '', '\uFEEE'), + # ARABIC LETTER (UIGHUR KAZAKH KIRGHIZ)? ALEF MAKSURA + '\u0649': ('\uFEEF', '\uFBE8', '\uFBE9', '\uFEF0'), + # ARABIC LETTER YEH + '\u064A': ('\uFEF1', '\uFEF3', '\uFEF4', '\uFEF2'), + # ARABIC LETTER ALEF WASLA + '\u0671': ('\uFB50', '', '', '\uFB51'), + # ARABIC LETTER U WITH HAMZA ABOVE + '\u0677': ('\uFBDD', '', '', ''), + # ARABIC LETTER TTEH + '\u0679': ('\uFB66', '\uFB68', '\uFB69', '\uFB67'), + # ARABIC LETTER TTEHEH + '\u067A': ('\uFB5E', '\uFB60', '\uFB61', '\uFB5F'), + # ARABIC LETTER BEEH + '\u067B': ('\uFB52', '\uFB54', '\uFB55', '\uFB53'), + # ARABIC LETTER PEH + '\u067E': ('\uFB56', '\uFB58', '\uFB59', '\uFB57'), + # ARABIC LETTER TEHEH + '\u067F': ('\uFB62', '\uFB64', '\uFB65', '\uFB63'), + # ARABIC LETTER BEHEH + '\u0680': ('\uFB5A', '\uFB5C', '\uFB5D', '\uFB5B'), + # ARABIC LETTER NYEH + '\u0683': ('\uFB76', '\uFB78', '\uFB79', '\uFB77'), + # ARABIC LETTER DYEH + '\u0684': ('\uFB72', '\uFB74', '\uFB75', '\uFB73'), + # ARABIC LETTER TCHEH + '\u0686': ('\uFB7A', '\uFB7C', '\uFB7D', '\uFB7B'), + # ARABIC LETTER TCHEHEH + '\u0687': ('\uFB7E', '\uFB80', '\uFB81', '\uFB7F'), + # ARABIC LETTER DDAL + '\u0688': ('\uFB88', '', '', '\uFB89'), + # ARABIC LETTER DAHAL + '\u068C': ('\uFB84', '', '', '\uFB85'), + # ARABIC LETTER DDAHAL + '\u068D': ('\uFB82', '', '', '\uFB83'), + # ARABIC LETTER DUL + '\u068E': ('\uFB86', '', '', '\uFB87'), + # ARABIC LETTER RREH + '\u0691': ('\uFB8C', '', '', '\uFB8D'), + # ARABIC LETTER JEH + '\u0698': ('\uFB8A', '', '', '\uFB8B'), + # ARABIC LETTER VEH + '\u06A4': ('\uFB6A', '\uFB6C', '\uFB6D', '\uFB6B'), + # ARABIC LETTER PEHEH + '\u06A6': ('\uFB6E', '\uFB70', '\uFB71', '\uFB6F'), + # ARABIC LETTER KEHEH + '\u06A9': ('\uFB8E', '\uFB90', '\uFB91', '\uFB8F'), + # ARABIC LETTER NG + '\u06AD': ('\uFBD3', '\uFBD5', '\uFBD6', '\uFBD4'), + # ARABIC LETTER GAF + '\u06AF': ('\uFB92', '\uFB94', '\uFB95', '\uFB93'), + # ARABIC LETTER NGOEH + '\u06B1': ('\uFB9A', '\uFB9C', '\uFB9D', '\uFB9B'), + # ARABIC LETTER GUEH + '\u06B3': ('\uFB96', '\uFB98', '\uFB99', '\uFB97'), + # ARABIC LETTER NOON GHUNNA + '\u06BA': ('\uFB9E', '', '', '\uFB9F'), + # ARABIC LETTER RNOON + '\u06BB': ('\uFBA0', '\uFBA2', '\uFBA3', '\uFBA1'), + # ARABIC LETTER HEH DOACHASHMEE + '\u06BE': ('\uFBAA', '\uFBAC', '\uFBAD', '\uFBAB'), + # ARABIC LETTER HEH WITH YEH ABOVE + '\u06C0': ('\uFBA4', '', '', '\uFBA5'), + # ARABIC LETTER HEH GOAL + '\u06C1': ('\uFBA6', '\uFBA8', '\uFBA9', '\uFBA7'), + # ARABIC LETTER KIRGHIZ OE + '\u06C5': ('\uFBE0', '', '', '\uFBE1'), + # ARABIC LETTER OE + '\u06C6': ('\uFBD9', '', '', '\uFBDA'), + # ARABIC LETTER U + '\u06C7': ('\uFBD7', '', '', '\uFBD8'), + # ARABIC LETTER YU + '\u06C8': ('\uFBDB', '', '', '\uFBDC'), + # ARABIC LETTER KIRGHIZ YU + '\u06C9': ('\uFBE2', '', '', '\uFBE3'), + # ARABIC LETTER VE + '\u06CB': ('\uFBDE', '', '', '\uFBDF'), + # ARABIC LETTER FARSI YEH + '\u06CC': ('\uFBFC', '\uFBFE', '\uFBFF', '\uFBFD'), + # ARABIC LETTER E + '\u06D0': ('\uFBE4', '\uFBE6', '\uFBE7', '\uFBE5'), + # ARABIC LETTER YEH BARREE + '\u06D2': ('\uFBAE', '', '', '\uFBAF'), + # ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + '\u06D3': ('\uFBB0', '', '', '\uFBB1'), + + # ZWJ + ZWJ: (ZWJ, ZWJ, ZWJ, ZWJ), +} + +LETTERS_ARABIC_V2 = { + # ARABIC LETTER HAMZA + '\u0621': ('\uFE80', '', '', ''), + # ARABIC LETTER ALEF WITH MADDA ABOVE + '\u0622': ('\u0622', '', '', '\uFE82'), + # ARABIC LETTER ALEF WITH HAMZA ABOVE + '\u0623': ('\u0623', '', '', '\uFE84'), + # ARABIC LETTER WAW WITH HAMZA ABOVE + '\u0624': ('\u0624', '', '', '\uFE86'), + # ARABIC LETTER ALEF WITH HAMZA BELOW + '\u0625': ('\u0625', '', '', '\uFE88'), + # ARABIC LETTER YEH WITH HAMZA ABOVE + '\u0626': ('\u0626', '\uFE8B', '\uFE8C', '\uFE8A'), + # ARABIC LETTER ALEF + '\u0627': ('\u0627', '', '', '\uFE8E'), + # ARABIC LETTER BEH + '\u0628': ('\u0628', '\uFE91', '\uFE92', '\uFE90'), + # ARABIC LETTER TEH MARBUTA + '\u0629': ('\u0629', '', '', '\uFE94'), + # ARABIC LETTER TEH + '\u062A': ('\u062A', '\uFE97', '\uFE98', '\uFE96'), + # ARABIC LETTER THEH + '\u062B': ('\u062B', '\uFE9B', '\uFE9C', '\uFE9A'), + # ARABIC LETTER JEEM + '\u062C': ('\u062C', '\uFE9F', '\uFEA0', '\uFE9E'), + # ARABIC LETTER HAH + '\u062D': ('\uFEA1', '\uFEA3', '\uFEA4', '\uFEA2'), + # ARABIC LETTER KHAH + '\u062E': ('\u062E', '\uFEA7', '\uFEA8', '\uFEA6'), + # ARABIC LETTER DAL + '\u062F': ('\u062F', '', '', '\uFEAA'), + # ARABIC LETTER THAL + '\u0630': ('\u0630', '', '', '\uFEAC'), + # ARABIC LETTER REH + '\u0631': ('\u0631', '', '', '\uFEAE'), + # ARABIC LETTER ZAIN + '\u0632': ('\u0632', '', '', '\uFEB0'), + # ARABIC LETTER SEEN + '\u0633': ('\u0633', '\uFEB3', '\uFEB4', '\uFEB2'), + # ARABIC LETTER SHEEN + '\u0634': ('\u0634', '\uFEB7', '\uFEB8', '\uFEB6'), + # ARABIC LETTER SAD + '\u0635': ('\u0635', '\uFEBB', '\uFEBC', '\uFEBA'), + # ARABIC LETTER DAD + '\u0636': ('\u0636', '\uFEBF', '\uFEC0', '\uFEBE'), + # ARABIC LETTER TAH + '\u0637': ('\u0637', '\uFEC3', '\uFEC4', '\uFEC2'), + # ARABIC LETTER ZAH + '\u0638': ('\u0638', '\uFEC7', '\uFEC8', '\uFEC6'), + # ARABIC LETTER AIN + '\u0639': ('\u0639', '\uFECB', '\uFECC', '\uFECA'), + # ARABIC LETTER GHAIN + '\u063A': ('\u063A', '\uFECF', '\uFED0', '\uFECE'), + # ARABIC TATWEEL + TATWEEL: (TATWEEL, TATWEEL, TATWEEL, TATWEEL), + # ARABIC LETTER FEH + '\u0641': ('\u0641', '\uFED3', '\uFED4', '\uFED2'), + # ARABIC LETTER QAF + '\u0642': ('\u0642', '\uFED7', '\uFED8', '\uFED6'), + # ARABIC LETTER KAF + '\u0643': ('\u0643', '\uFEDB', '\uFEDC', '\uFEDA'), + # ARABIC LETTER LAM + '\u0644': ('\u0644', '\uFEDF', '\uFEE0', '\uFEDE'), + # ARABIC LETTER MEEM + '\u0645': ('\u0645', '\uFEE3', '\uFEE4', '\uFEE2'), + # ARABIC LETTER NOON + '\u0646': ('\u0646', '\uFEE7', '\uFEE8', '\uFEE6'), + # ARABIC LETTER HEH + '\u0647': ('\u0647', '\uFEEB', '\uFEEC', '\uFEEA'), + # ARABIC LETTER WAW + '\u0648': ('\u0648', '', '', '\uFEEE'), + # ARABIC LETTER (UIGHUR KAZAKH KIRGHIZ)? ALEF MAKSURA + '\u0649': ('\u0649', '\uFBE8', '\uFBE9', '\uFEF0'), + # ARABIC LETTER YEH + '\u064A': ('\u064A', '\uFEF3', '\uFEF4', '\uFEF2'), + # ARABIC LETTER ALEF WASLA + '\u0671': ('\u0671', '', '', '\uFB51'), + # ARABIC LETTER U WITH HAMZA ABOVE + '\u0677': ('\u0677', '', '', ''), + # ARABIC LETTER TTEH + '\u0679': ('\u0679', '\uFB68', '\uFB69', '\uFB67'), + # ARABIC LETTER TTEHEH + '\u067A': ('\u067A', '\uFB60', '\uFB61', '\uFB5F'), + # ARABIC LETTER BEEH + '\u067B': ('\u067B', '\uFB54', '\uFB55', '\uFB53'), + # ARABIC LETTER PEH + '\u067E': ('\u067E', '\uFB58', '\uFB59', '\uFB57'), + # ARABIC LETTER TEHEH + '\u067F': ('\u067F', '\uFB64', '\uFB65', '\uFB63'), + # ARABIC LETTER BEHEH + '\u0680': ('\u0680', '\uFB5C', '\uFB5D', '\uFB5B'), + # ARABIC LETTER NYEH + '\u0683': ('\u0683', '\uFB78', '\uFB79', '\uFB77'), + # ARABIC LETTER DYEH + '\u0684': ('\u0684', '\uFB74', '\uFB75', '\uFB73'), + # ARABIC LETTER TCHEH + '\u0686': ('\u0686', '\uFB7C', '\uFB7D', '\uFB7B'), + # ARABIC LETTER TCHEHEH + '\u0687': ('\u0687', '\uFB80', '\uFB81', '\uFB7F'), + # ARABIC LETTER DDAL + '\u0688': ('\u0688', '', '', '\uFB89'), + # ARABIC LETTER DAHAL + '\u068C': ('\u068C', '', '', '\uFB85'), + # ARABIC LETTER DDAHAL + '\u068D': ('\u068D', '', '', '\uFB83'), + # ARABIC LETTER DUL + '\u068E': ('\u068E', '', '', '\uFB87'), + # ARABIC LETTER RREH + '\u0691': ('\u0691', '', '', '\uFB8D'), + # ARABIC LETTER JEH + '\u0698': ('\u0698', '', '', '\uFB8B'), + # ARABIC LETTER VEH + '\u06A4': ('\u06A4', '\uFB6C', '\uFB6D', '\uFB6B'), + # ARABIC LETTER PEHEH + '\u06A6': ('\u06A6', '\uFB70', '\uFB71', '\uFB6F'), + # ARABIC LETTER KEHEH + '\u06A9': ('\u06A9', '\uFB90', '\uFB91', '\uFB8F'), + # ARABIC LETTER NG + '\u06AD': ('\u06AD', '\uFBD5', '\uFBD6', '\uFBD4'), + # ARABIC LETTER GAF + '\u06AF': ('\u06AF', '\uFB94', '\uFB95', '\uFB93'), + # ARABIC LETTER NGOEH + '\u06B1': ('\u06B1', '\uFB9C', '\uFB9D', '\uFB9B'), + # ARABIC LETTER GUEH + '\u06B3': ('\u06B3', '\uFB98', '\uFB99', '\uFB97'), + # ARABIC LETTER NOON GHUNNA + '\u06BA': ('\u06BA', '', '', '\uFB9F'), + # ARABIC LETTER RNOON + '\u06BB': ('\u06BB', '\uFBA2', '\uFBA3', '\uFBA1'), + # ARABIC LETTER HEH DOACHASHMEE + '\u06BE': ('\u06BE', '\uFBAC', '\uFBAD', '\uFBAB'), + # ARABIC LETTER HEH WITH YEH ABOVE + '\u06C0': ('\u06C0', '', '', '\uFBA5'), + # ARABIC LETTER HEH GOAL + '\u06C1': ('\u06C1', '\uFBA8', '\uFBA9', '\uFBA7'), + # ARABIC LETTER KIRGHIZ OE + '\u06C5': ('\u06C5', '', '', '\uFBE1'), + # ARABIC LETTER OE + '\u06C6': ('\u06C6', '', '', '\uFBDA'), + # ARABIC LETTER U + '\u06C7': ('\u06C7', '', '', '\uFBD8'), + # ARABIC LETTER YU + '\u06C8': ('\u06C8', '', '', '\uFBDC'), + # ARABIC LETTER KIRGHIZ YU + '\u06C9': ('\u06C9', '', '', '\uFBE3'), + # ARABIC LETTER VE + '\u06CB': ('\u06CB', '', '', '\uFBDF'), + # ARABIC LETTER FARSI YEH + '\u06CC': ('\u06CC', '\uFBFE', '\uFBFF', '\uFBFD'), + # ARABIC LETTER E + '\u06D0': ('\u06D0', '\uFBE6', '\uFBE7', '\uFBE5'), + # ARABIC LETTER YEH BARREE + '\u06D2': ('\u06D2', '', '', '\uFBAF'), + # ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + '\u06D3': ('\u06D3', '', '', '\uFBB1'), + # Kurdish letter YEAH + '\u06ce': ('\uE004', '\uE005', '\uE006', '\uE004'), + # Kurdish letter Hamza same as arabic Teh without the point + '\u06d5': ('\u06d5', '', '', '\uE000'), + # ZWJ + ZWJ: (ZWJ, ZWJ, ZWJ, ZWJ), +} +LETTERS_KURDISH = { + # ARABIC LETTER HAMZA + '\u0621': ('\uFE80', '', '', ''), + # ARABIC LETTER ALEF WITH MADDA ABOVE + '\u0622': ('\u0622', '', '', '\uFE82'), + # ARABIC LETTER ALEF WITH HAMZA ABOVE + '\u0623': ('\u0623', '', '', '\uFE84'), + # ARABIC LETTER WAW WITH HAMZA ABOVE + '\u0624': ('\u0624', '', '', '\uFE86'), + # ARABIC LETTER ALEF WITH HAMZA BELOW + '\u0625': ('\u0625', '', '', '\uFE88'), + # ARABIC LETTER YEH WITH HAMZA ABOVE + '\u0626': ('\u0626', '\uFE8B', '\uFE8C', '\uFE8A'), + # ARABIC LETTER ALEF + '\u0627': ('\u0627', '', '', '\uFE8E'), + # ARABIC LETTER BEH + '\u0628': ('\u0628', '\uFE91', '\uFE92', '\uFE90'), + # ARABIC LETTER TEH MARBUTA + '\u0629': ('\u0629', '', '', '\uFE94'), + # ARABIC LETTER TEH + '\u062A': ('\u062A', '\uFE97', '\uFE98', '\uFE96'), + # ARABIC LETTER THEH + '\u062B': ('\u062B', '\uFE9B', '\uFE9C', '\uFE9A'), + # ARABIC LETTER JEEM + '\u062C': ('\u062C', '\uFE9F', '\uFEA0', '\uFE9E'), + # ARABIC LETTER HAH + '\u062D': ('\uFEA1', '\uFEA3', '\uFEA4', '\uFEA2'), + # ARABIC LETTER KHAH + '\u062E': ('\u062E', '\uFEA7', '\uFEA8', '\uFEA6'), + # ARABIC LETTER DAL + '\u062F': ('\u062F', '', '', '\uFEAA'), + # ARABIC LETTER THAL + '\u0630': ('\u0630', '', '', '\uFEAC'), + # ARABIC LETTER REH + '\u0631': ('\u0631', '', '', '\uFEAE'), + # ARABIC LETTER ZAIN + '\u0632': ('\u0632', '', '', '\uFEB0'), + # ARABIC LETTER SEEN + '\u0633': ('\u0633', '\uFEB3', '\uFEB4', '\uFEB2'), + # ARABIC LETTER SHEEN + '\u0634': ('\u0634', '\uFEB7', '\uFEB8', '\uFEB6'), + # ARABIC LETTER SAD + '\u0635': ('\u0635', '\uFEBB', '\uFEBC', '\uFEBA'), + # ARABIC LETTER DAD + '\u0636': ('\u0636', '\uFEBF', '\uFEC0', '\uFEBE'), + # ARABIC LETTER TAH + '\u0637': ('\u0637', '\uFEC3', '\uFEC4', '\uFEC2'), + # ARABIC LETTER ZAH + '\u0638': ('\u0638', '\uFEC7', '\uFEC8', '\uFEC6'), + # ARABIC LETTER AIN + '\u0639': ('\u0639', '\uFECB', '\uFECC', '\uFECA'), + # ARABIC LETTER GHAIN + '\u063A': ('\u063A', '\uFECF', '\uFED0', '\uFECE'), + # ARABIC TATWEEL + TATWEEL: (TATWEEL, TATWEEL, TATWEEL, TATWEEL), + # ARABIC LETTER FEH + '\u0641': ('\u0641', '\uFED3', '\uFED4', '\uFED2'), + # ARABIC LETTER QAF + '\u0642': ('\u0642', '\uFED7', '\uFED8', '\uFED6'), + # ARABIC LETTER KAF + '\u0643': ('\u0643', '\uFEDB', '\uFEDC', '\uFEDA'), + # ARABIC LETTER LAM + '\u0644': ('\u0644', '\uFEDF', '\uFEE0', '\uFEDE'), + # ARABIC LETTER MEEM + '\u0645': ('\u0645', '\uFEE3', '\uFEE4', '\uFEE2'), + # ARABIC LETTER NOON + '\u0646': ('\u0646', '\uFEE7', '\uFEE8', '\uFEE6'), + # ARABIC LETTER HEH + '\u0647': ('\uFBAB', '\uFBAB', '\uFBAB', '\uFBAB'), + # ARABIC LETTER WAW + '\u0648': ('\u0648', '', '', '\uFEEE'), + # ARABIC LETTER (UIGHUR KAZAKH KIRGHIZ)? ALEF MAKSURA + '\u0649': ('\u0649', '\uFBE8', '\uFBE9', '\uFEF0'), + # ARABIC LETTER YEH + '\u064A': ('\u064A', '\uFEF3', '\uFEF4', '\uFEF2'), + # ARABIC LETTER ALEF WASLA + '\u0671': ('\u0671', '', '', '\uFB51'), + # ARABIC LETTER U WITH HAMZA ABOVE + '\u0677': ('\u0677', '', '', ''), + # ARABIC LETTER TTEH + '\u0679': ('\u0679', '\uFB68', '\uFB69', '\uFB67'), + # ARABIC LETTER TTEHEH + '\u067A': ('\u067A', '\uFB60', '\uFB61', '\uFB5F'), + # ARABIC LETTER BEEH + '\u067B': ('\u067B', '\uFB54', '\uFB55', '\uFB53'), + # ARABIC LETTER PEH + '\u067E': ('\u067E', '\uFB58', '\uFB59', '\uFB57'), + # ARABIC LETTER TEHEH + '\u067F': ('\u067F', '\uFB64', '\uFB65', '\uFB63'), + # ARABIC LETTER BEHEH + '\u0680': ('\u0680', '\uFB5C', '\uFB5D', '\uFB5B'), + # ARABIC LETTER NYEH + '\u0683': ('\u0683', '\uFB78', '\uFB79', '\uFB77'), + # ARABIC LETTER DYEH + '\u0684': ('\u0684', '\uFB74', '\uFB75', '\uFB73'), + # ARABIC LETTER TCHEH + '\u0686': ('\u0686', '\uFB7C', '\uFB7D', '\uFB7B'), + # ARABIC LETTER TCHEHEH + '\u0687': ('\u0687', '\uFB80', '\uFB81', '\uFB7F'), + # ARABIC LETTER DDAL + '\u0688': ('\u0688', '', '', '\uFB89'), + # ARABIC LETTER DAHAL + '\u068C': ('\u068C', '', '', '\uFB85'), + # ARABIC LETTER DDAHAL + '\u068D': ('\u068D', '', '', '\uFB83'), + # ARABIC LETTER DUL + '\u068E': ('\u068E', '', '', '\uFB87'), + # ARABIC LETTER RREH + '\u0691': ('\u0691', '', '', '\uFB8D'), + # ARABIC LETTER JEH + '\u0698': ('\u0698', '', '', '\uFB8B'), + # ARABIC LETTER VEH + '\u06A4': ('\u06A4', '\uFB6C', '\uFB6D', '\uFB6B'), + # ARABIC LETTER PEHEH + '\u06A6': ('\u06A6', '\uFB70', '\uFB71', '\uFB6F'), + # ARABIC LETTER KEHEH + '\u06A9': ('\u06A9', '\uFB90', '\uFB91', '\uFB8F'), + # ARABIC LETTER NG + '\u06AD': ('\u06AD', '\uFBD5', '\uFBD6', '\uFBD4'), + # ARABIC LETTER GAF + '\u06AF': ('\u06AF', '\uFB94', '\uFB95', '\uFB93'), + # ARABIC LETTER NGOEH + '\u06B1': ('\u06B1', '\uFB9C', '\uFB9D', '\uFB9B'), + # ARABIC LETTER GUEH + '\u06B3': ('\u06B3', '\uFB98', '\uFB99', '\uFB97'), + # ARABIC LETTER NOON GHUNNA + '\u06BA': ('\u06BA', '', '', '\uFB9F'), + # ARABIC LETTER RNOON + '\u06BB': ('\u06BB', '\uFBA2', '\uFBA3', '\uFBA1'), + # ARABIC LETTER HEH DOACHASHMEE + '\u06BE': ('\u06BE', '\uFBAC', '\uFBAD', '\uFBAB'), + # ARABIC LETTER HEH WITH YEH ABOVE + '\u06C0': ('\u06C0', '', '', '\uFBA5'), + # ARABIC LETTER HEH GOAL + '\u06C1': ('\u06C1', '\uFBA8', '\uFBA9', '\uFBA7'), + # ARABIC LETTER KIRGHIZ OE + '\u06C5': ('\u06C5', '', '', '\uFBE1'), + # ARABIC LETTER OE + '\u06C6': ('\u06C6', '', '', '\uFBDA'), + # ARABIC LETTER U + '\u06C7': ('\u06C7', '', '', '\uFBD8'), + # ARABIC LETTER YU + '\u06C8': ('\u06C8', '', '', '\uFBDC'), + # ARABIC LETTER KIRGHIZ YU + '\u06C9': ('\u06C9', '', '', '\uFBE3'), + # ARABIC LETTER VE + '\u06CB': ('\u06CB', '', '', '\uFBDF'), + # ARABIC LETTER FARSI YEH + '\u06CC': ('\u06CC', '\uFBFE', '\uFBFF', '\uFBFD'), + # ARABIC LETTER E + '\u06D0': ('\u06D0', '\uFBE6', '\uFBE7', '\uFBE5'), + # ARABIC LETTER YEH BARREE + '\u06D2': ('\u06D2', '', '', '\uFBAF'), + # ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + '\u06D3': ('\u06D3', '', '', '\uFBB1'), + # Kurdish letter YEAH + '\u06ce': ('\uE004', '\uE005', '\uE006', '\uE004'), + # Kurdish letter Hamza same as arabic Teh without the point + '\u06d5': ('\u06d5', '', '', '\uE000'), + # ZWJ + ZWJ: (ZWJ, ZWJ, ZWJ, ZWJ), +} + +def connects_with_letter_before(letter,LETTERS): + if letter not in LETTERS: + return False + forms = LETTERS[letter] + return forms[FINAL] or forms[MEDIAL] + + +def connects_with_letter_after(letter,LETTERS): + if letter not in LETTERS: + return False + forms = LETTERS[letter] + return forms[INITIAL] or forms[MEDIAL] + + +def connects_with_letters_before_and_after(letter,LETTERS): + if letter not in LETTERS: + return False + forms = LETTERS[letter] + return forms[MEDIAL] diff --git a/exe_generator/arabic_reshaper/ligatures.py b/exe_generator/arabic_reshaper/ligatures.py new file mode 100644 index 0000000..64cc9ec --- /dev/null +++ b/exe_generator/arabic_reshaper/ligatures.py @@ -0,0 +1,933 @@ +# Each ligature is of the format: +# +# ('', ) +# +# Where is used in the configuration and is of the format: +# +# ('', ('', '', '', '')) +# +# Where is the string to replace, and is the replacement in +# case was in isolated form, is the replacement in case +# was in initial form, is the replacement in case was +# in medial form, and is the replacement in case was in final +# form. If no replacement is specified for a form, then no replacement of +# will occur. + +# Order here is important, it should be: +# 1. Sentences +# 2. Words +# 3. Letters +# This way we make sure we replace the longest ligatures first + +from __future__ import unicode_literals +from itertools import chain + +SENTENCES_LIGATURES = ( + ('ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM', ( + '\u0628\u0633\u0645\u0020' + '\u0627\u0644\u0644\u0647\u0020' + '\u0627\u0644\u0631\u062D\u0645\u0646\u0020' + '\u0627\u0644\u0631\u062D\u064A\u0645', + + ('\uFDFD', '', '', '') + )), + ('ARABIC LIGATURE JALLAJALALOUHOU', ( + '\u062C\u0644\u0020\u062C\u0644\u0627\u0644\u0647', + + ('\uFDFB', '', '', '') + )), + ('ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM', ( + '\u0635\u0644\u0649\u0020' + '\u0627\u0644\u0644\u0647\u0020' + '\u0639\u0644\u064A\u0647\u0020' + '\u0648\u0633\u0644\u0645', + + ('\uFDFA', '', '', '') + )), +) + +WORDS_LIGATURES = ( + ('ARABIC LIGATURE ALLAH', ( + '\u0627\u0644\u0644\u0647', ('\uFDF2', '', '', ''), + )), + ('ARABIC LIGATURE AKBAR', ( + '\u0623\u0643\u0628\u0631', ('\uFDF3', '', '', ''), + )), + ('ARABIC LIGATURE ALAYHE', ( + '\u0639\u0644\u064A\u0647', ('\uFDF7', '', '', ''), + )), + ('ARABIC LIGATURE MOHAMMAD', ( + '\u0645\u062D\u0645\u062F', ('\uFDF4', '', '', ''), + )), + ('ARABIC LIGATURE RASOUL', ( + '\u0631\u0633\u0648\u0644', ('\uFDF6', '', '', ''), + )), + ('ARABIC LIGATURE SALAM', ( + '\u0635\u0644\u0639\u0645', ('\uFDF5', '', '', ''), + )), + ('ARABIC LIGATURE SALLA', ( + '\u0635\u0644\u0649', ('\uFDF9', '', '', ''), + )), + ('ARABIC LIGATURE WASALLAM', ( + '\u0648\u0633\u0644\u0645', ('\uFDF8', '', '', ''), + )), + ('RIAL SIGN', ( + '\u0631[\u06CC\u064A]\u0627\u0644', ('\uFDFC', '', '', ''), + )), +) + +LETTERS_LIGATURES = ( + ('ARABIC LIGATURE AIN WITH ALEF MAKSURA', ( + '\u0639\u0649', ('\uFCF7', '', '', '\uFD13'), + )), + ('ARABIC LIGATURE AIN WITH JEEM', ( + '\u0639\u062C', ('\uFC29', '\uFCBA', '', ''), + )), + ('ARABIC LIGATURE AIN WITH JEEM WITH MEEM', ( + '\u0639\u062C\u0645', ('', '\uFDC4', '', '\uFD75'), + )), + ('ARABIC LIGATURE AIN WITH MEEM', ( + '\u0639\u0645', ('\uFC2A', '\uFCBB', '', ''), + )), + ('ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA', ( + '\u0639\u0645\u0649', ('', '', '', '\uFD78'), + )), + ('ARABIC LIGATURE AIN WITH MEEM WITH MEEM', ( + '\u0639\u0645\u0645', ('', '\uFD77', '', '\uFD76'), + )), + ('ARABIC LIGATURE AIN WITH MEEM WITH YEH', ( + '\u0639\u0645\u064A', ('', '', '', '\uFDB6'), + )), + ('ARABIC LIGATURE AIN WITH YEH', ( + '\u0639\u064A', ('\uFCF8', '', '', '\uFD14'), + )), + ('ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF', ( + '\u0649\u0670', ('\uFC5D', '', '', '\uFC90'), + )), + ('ARABIC LIGATURE ALEF WITH FATHATAN', ( + '\u0627\u064B', ('\uFD3D', '', '', '\uFD3C'), + )), + ('ARABIC LIGATURE BEH WITH ALEF MAKSURA', ( + '\u0628\u0649', ('\uFC09', '', '', '\uFC6E'), + )), + ('ARABIC LIGATURE BEH WITH HAH', ( + '\u0628\u062D', ('\uFC06', '\uFC9D', '', ''), + )), + ('ARABIC LIGATURE BEH WITH HAH WITH YEH', ( + '\u0628\u062D\u064A', ('', '', '', '\uFDC2'), + )), + ('ARABIC LIGATURE BEH WITH HEH', ( + '\u0628\u0647', ('', '\uFCA0', '\uFCE2', ''), + )), + ('ARABIC LIGATURE BEH WITH JEEM', ( + '\u0628\u062C', ('\uFC05', '\uFC9C', '', ''), + )), + ('ARABIC LIGATURE BEH WITH KHAH', ( + '\u0628\u062E', ('\uFC07', '\uFC9E', '', ''), + )), + ('ARABIC LIGATURE BEH WITH KHAH WITH YEH', ( + '\u0628\u062E\u064A', ('', '', '', '\uFD9E'), + )), + ('ARABIC LIGATURE BEH WITH MEEM', ( + '\u0628\u0645', ('\uFC08', '\uFC9F', '\uFCE1', '\uFC6C'), + )), + ('ARABIC LIGATURE BEH WITH NOON', ( + '\u0628\u0646', ('', '', '', '\uFC6D'), + )), + ('ARABIC LIGATURE BEH WITH REH', ( + '\u0628\u0631', ('', '', '', '\uFC6A'), + )), + ('ARABIC LIGATURE BEH WITH YEH', ( + '\u0628\u064A', ('\uFC0A', '', '', '\uFC6F'), + )), + ('ARABIC LIGATURE BEH WITH ZAIN', ( + '\u0628\u0632', ('', '', '', '\uFC6B'), + )), + ('ARABIC LIGATURE DAD WITH ALEF MAKSURA', ( + '\u0636\u0649', ('\uFD07', '', '', '\uFD23'), + )), + ('ARABIC LIGATURE DAD WITH HAH', ( + '\u0636\u062D', ('\uFC23', '\uFCB5', '', ''), + )), + ('ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA', ( + '\u0636\u062D\u0649', ('', '', '', '\uFD6E'), + )), + ('ARABIC LIGATURE DAD WITH HAH WITH YEH', ( + '\u0636\u062D\u064A', ('', '', '', '\uFDAB'), + )), + ('ARABIC LIGATURE DAD WITH JEEM', ( + '\u0636\u062C', ('\uFC22', '\uFCB4', '', ''), + )), + ('ARABIC LIGATURE DAD WITH KHAH', ( + '\u0636\u062E', ('\uFC24', '\uFCB6', '', ''), + )), + ('ARABIC LIGATURE DAD WITH KHAH WITH MEEM', ( + '\u0636\u062E\u0645', ('', '\uFD70', '', '\uFD6F'), + )), + ('ARABIC LIGATURE DAD WITH MEEM', ( + '\u0636\u0645', ('\uFC25', '\uFCB7', '', ''), + )), + ('ARABIC LIGATURE DAD WITH REH', ( + '\u0636\u0631', ('\uFD10', '', '', '\uFD2C'), + )), + ('ARABIC LIGATURE DAD WITH YEH', ( + '\u0636\u064A', ('\uFD08', '', '', '\uFD24'), + )), + ('ARABIC LIGATURE FEH WITH ALEF MAKSURA', ( + '\u0641\u0649', ('\uFC31', '', '', '\uFC7C'), + )), + ('ARABIC LIGATURE FEH WITH HAH', ( + '\u0641\u062D', ('\uFC2E', '\uFCBF', '', ''), + )), + ('ARABIC LIGATURE FEH WITH JEEM', ( + '\u0641\u062C', ('\uFC2D', '\uFCBE', '', ''), + )), + ('ARABIC LIGATURE FEH WITH KHAH', ( + '\u0641\u062E', ('\uFC2F', '\uFCC0', '', ''), + )), + ('ARABIC LIGATURE FEH WITH KHAH WITH MEEM', ( + '\u0641\u062E\u0645', ('', '\uFD7D', '', '\uFD7C'), + )), + ('ARABIC LIGATURE FEH WITH MEEM', ( + '\u0641\u0645', ('\uFC30', '\uFCC1', '', ''), + )), + ('ARABIC LIGATURE FEH WITH MEEM WITH YEH', ( + '\u0641\u0645\u064A', ('', '', '', '\uFDC1'), + )), + ('ARABIC LIGATURE FEH WITH YEH', ( + '\u0641\u064A', ('\uFC32', '', '', '\uFC7D'), + )), + ('ARABIC LIGATURE GHAIN WITH ALEF MAKSURA', ( + '\u063A\u0649', ('\uFCF9', '', '', '\uFD15'), + )), + ('ARABIC LIGATURE GHAIN WITH JEEM', ( + '\u063A\u062C', ('\uFC2B', '\uFCBC', '', ''), + )), + ('ARABIC LIGATURE GHAIN WITH MEEM', ( + '\u063A\u0645', ('\uFC2C', '\uFCBD', '', ''), + )), + ('ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA', ( + '\u063A\u0645\u0649', ('', '', '', '\uFD7B'), + )), + ('ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM', ( + '\u063A\u0645\u0645', ('', '', '', '\uFD79'), + )), + ('ARABIC LIGATURE GHAIN WITH MEEM WITH YEH', ( + '\u063A\u0645\u064A', ('', '', '', '\uFD7A'), + )), + ('ARABIC LIGATURE GHAIN WITH YEH', ( + '\u063A\u064A', ('\uFCFA', '', '', '\uFD16'), + )), + ('ARABIC LIGATURE HAH WITH ALEF MAKSURA', ( + '\u062D\u0649', ('\uFCFF', '', '', '\uFD1B'), + )), + ('ARABIC LIGATURE HAH WITH JEEM', ( + '\u062D\u062C', ('\uFC17', '\uFCA9', '', ''), + )), + ('ARABIC LIGATURE HAH WITH JEEM WITH YEH', ( + '\u062D\u062C\u064A', ('', '', '', '\uFDBF'), + )), + ('ARABIC LIGATURE HAH WITH MEEM', ( + '\u062D\u0645', ('\uFC18', '\uFCAA', '', ''), + )), + ('ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA', ( + '\u062D\u0645\u0649', ('', '', '', '\uFD5B'), + )), + ('ARABIC LIGATURE HAH WITH MEEM WITH YEH', ( + '\u062D\u0645\u064A', ('', '', '', '\uFD5A'), + )), + ('ARABIC LIGATURE HAH WITH YEH', ( + '\u062D\u064A', ('\uFD00', '', '', '\uFD1C'), + )), + ('ARABIC LIGATURE HEH WITH ALEF MAKSURA', ( + '\u0647\u0649', ('\uFC53', '', '', ''), + )), + ('ARABIC LIGATURE HEH WITH JEEM', ( + '\u0647\u062C', ('\uFC51', '\uFCD7', '', ''), + )), + ('ARABIC LIGATURE HEH WITH MEEM', ( + '\u0647\u0645', ('\uFC52', '\uFCD8', '', ''), + )), + ('ARABIC LIGATURE HEH WITH MEEM WITH JEEM', ( + '\u0647\u0645\u062C', ('', '\uFD93', '', ''), + )), + ('ARABIC LIGATURE HEH WITH MEEM WITH MEEM', ( + '\u0647\u0645\u0645', ('', '\uFD94', '', ''), + )), + ('ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF', ( + '\u0647\u0670', ('', '\uFCD9', '', ''), + )), + ('ARABIC LIGATURE HEH WITH YEH', ( + '\u0647\u064A', ('\uFC54', '', '', ''), + )), + ('ARABIC LIGATURE JEEM WITH ALEF MAKSURA', ( + '\u062C\u0649', ('\uFD01', '', '', '\uFD1D'), + )), + ('ARABIC LIGATURE JEEM WITH HAH', ( + '\u062C\u062D', ('\uFC15', '\uFCA7', '', ''), + )), + ('ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA', ( + '\u062C\u062D\u0649', ('', '', '', '\uFDA6'), + )), + ('ARABIC LIGATURE JEEM WITH HAH WITH YEH', ( + '\u062C\u062D\u064A', ('', '', '', '\uFDBE'), + )), + ('ARABIC LIGATURE JEEM WITH MEEM', ( + '\u062C\u0645', ('\uFC16', '\uFCA8', '', ''), + )), + ('ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA', ( + '\u062C\u0645\u0649', ('', '', '', '\uFDA7'), + )), + ('ARABIC LIGATURE JEEM WITH MEEM WITH HAH', ( + '\u062C\u0645\u062D', ('', '\uFD59', '', '\uFD58'), + )), + ('ARABIC LIGATURE JEEM WITH MEEM WITH YEH', ( + '\u062C\u0645\u064A', ('', '', '', '\uFDA5'), + )), + ('ARABIC LIGATURE JEEM WITH YEH', ( + '\u062C\u064A', ('\uFD02', '', '', '\uFD1E'), + )), + ('ARABIC LIGATURE KAF WITH ALEF', ( + '\u0643\u0627', ('\uFC37', '', '', '\uFC80'), + )), + ('ARABIC LIGATURE KAF WITH ALEF MAKSURA', ( + '\u0643\u0649', ('\uFC3D', '', '', '\uFC83'), + )), + ('ARABIC LIGATURE KAF WITH HAH', ( + '\u0643\u062D', ('\uFC39', '\uFCC5', '', ''), + )), + ('ARABIC LIGATURE KAF WITH JEEM', ( + '\u0643\u062C', ('\uFC38', '\uFCC4', '', ''), + )), + ('ARABIC LIGATURE KAF WITH KHAH', ( + '\u0643\u062E', ('\uFC3A', '\uFCC6', '', ''), + )), + ('ARABIC LIGATURE KAF WITH LAM', ( + '\u0643\u0644', ('\uFC3B', '\uFCC7', '\uFCEB', '\uFC81'), + )), + ('ARABIC LIGATURE KAF WITH MEEM', ( + '\u0643\u0645', ('\uFC3C', '\uFCC8', '\uFCEC', '\uFC82'), + )), + ('ARABIC LIGATURE KAF WITH MEEM WITH MEEM', ( + '\u0643\u0645\u0645', ('', '\uFDC3', '', '\uFDBB'), + )), + ('ARABIC LIGATURE KAF WITH MEEM WITH YEH', ( + '\u0643\u0645\u064A', ('', '', '', '\uFDB7'), + )), + ('ARABIC LIGATURE KAF WITH YEH', ( + '\u0643\u064A', ('\uFC3E', '', '', '\uFC84'), + )), + ('ARABIC LIGATURE KHAH WITH ALEF MAKSURA', ( + '\u062E\u0649', ('\uFD03', '', '', '\uFD1F'), + )), + ('ARABIC LIGATURE KHAH WITH HAH', ( + '\u062E\u062D', ('\uFC1A', '', '', ''), + )), + ('ARABIC LIGATURE KHAH WITH JEEM', ( + '\u062E\u062C', ('\uFC19', '\uFCAB', '', ''), + )), + ('ARABIC LIGATURE KHAH WITH MEEM', ( + '\u062E\u0645', ('\uFC1B', '\uFCAC', '', ''), + )), + ('ARABIC LIGATURE KHAH WITH YEH', ( + '\u062E\u064A', ('\uFD04', '', '', '\uFD20'), + )), + ('ARABIC LIGATURE LAM WITH ALEF', ( + '\u0644\u0627', ('\uFEFB', '', '', '\uFEFC'), + )), + ('ARABIC LIGATURE LAM WITH ALEF MAKSURA', ( + '\u0644\u0649', ('\uFC43', '', '', '\uFC86'), + )), + ('ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE', ( + '\u0644\u0623', ('\uFEF7', '', '', '\uFEF8'), + )), + ('ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW', ( + '\u0644\u0625', ('\uFEF9', '', '', '\uFEFA'), + )), + ('ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE', ( + '\u0644\u0622', ('\uFEF5', '', '', '\uFEF6'), + )), + ('ARABIC LIGATURE LAM WITH HAH', ( + '\u0644\u062D', ('\uFC40', '\uFCCA', '', ''), + )), + ('ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA', ( + '\u0644\u062D\u0649', ('', '', '', '\uFD82'), + )), + ('ARABIC LIGATURE LAM WITH HAH WITH MEEM', ( + '\u0644\u062D\u0645', ('', '\uFDB5', '', '\uFD80'), + )), + ('ARABIC LIGATURE LAM WITH HAH WITH YEH', ( + '\u0644\u062D\u064A', ('', '', '', '\uFD81'), + )), + ('ARABIC LIGATURE LAM WITH HEH', ( + '\u0644\u0647', ('', '\uFCCD', '', ''), + )), + ('ARABIC LIGATURE LAM WITH JEEM', ( + '\u0644\u062C', ('\uFC3F', '\uFCC9', '', ''), + )), + ('ARABIC LIGATURE LAM WITH JEEM WITH JEEM', ( + '\u0644\u062C\u062C', ('', '\uFD83', '', '\uFD84'), + )), + ('ARABIC LIGATURE LAM WITH JEEM WITH MEEM', ( + '\u0644\u062C\u0645', ('', '\uFDBA', '', '\uFDBC'), + )), + ('ARABIC LIGATURE LAM WITH JEEM WITH YEH', ( + '\u0644\u062C\u064A', ('', '', '', '\uFDAC'), + )), + ('ARABIC LIGATURE LAM WITH KHAH', ( + '\u0644\u062E', ('\uFC41', '\uFCCB', '', ''), + )), + ('ARABIC LIGATURE LAM WITH KHAH WITH MEEM', ( + '\u0644\u062E\u0645', ('', '\uFD86', '', '\uFD85'), + )), + ('ARABIC LIGATURE LAM WITH MEEM', ( + '\u0644\u0645', ('\uFC42', '\uFCCC', '\uFCED', '\uFC85'), + )), + ('ARABIC LIGATURE LAM WITH MEEM WITH HAH', ( + '\u0644\u0645\u062D', ('', '\uFD88', '', '\uFD87'), + )), + ('ARABIC LIGATURE LAM WITH MEEM WITH YEH', ( + '\u0644\u0645\u064A', ('', '', '', '\uFDAD'), + )), + ('ARABIC LIGATURE LAM WITH YEH', ( + '\u0644\u064A', ('\uFC44', '', '', '\uFC87'), + )), + ('ARABIC LIGATURE MEEM WITH ALEF', ( + '\u0645\u0627', ('', '', '', '\uFC88'), + )), + ('ARABIC LIGATURE MEEM WITH ALEF MAKSURA', ( + '\u0645\u0649', ('\uFC49', '', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH HAH', ( + '\u0645\u062D', ('\uFC46', '\uFCCF', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH HAH WITH JEEM', ( + '\u0645\u062D\u062C', ('', '\uFD89', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH HAH WITH MEEM', ( + '\u0645\u062D\u0645', ('', '\uFD8A', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH HAH WITH YEH', ( + '\u0645\u062D\u064A', ('', '', '', '\uFD8B'), + )), + ('ARABIC LIGATURE MEEM WITH JEEM', ( + '\u0645\u062C', ('\uFC45', '\uFCCE', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH JEEM WITH HAH', ( + '\u0645\u062C\u062D', ('', '\uFD8C', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH JEEM WITH KHAH', ( + '\u0645\u062C\u062E', ('', '\uFD92', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH JEEM WITH MEEM', ( + '\u0645\u062C\u0645', ('', '\uFD8D', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH JEEM WITH YEH', ( + '\u0645\u062C\u064A', ('', '', '', '\uFDC0'), + )), + ('ARABIC LIGATURE MEEM WITH KHAH', ( + '\u0645\u062E', ('\uFC47', '\uFCD0', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH KHAH WITH JEEM', ( + '\u0645\u062E\u062C', ('', '\uFD8E', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH KHAH WITH MEEM', ( + '\u0645\u062E\u0645', ('', '\uFD8F', '', ''), + )), + ('ARABIC LIGATURE MEEM WITH KHAH WITH YEH', ( + '\u0645\u062E\u064A', ('', '', '', '\uFDB9'), + )), + ('ARABIC LIGATURE MEEM WITH MEEM', ( + '\u0645\u0645', ('\uFC48', '\uFCD1', '', '\uFC89'), + )), + ('ARABIC LIGATURE MEEM WITH MEEM WITH YEH', ( + '\u0645\u0645\u064A', ('', '', '', '\uFDB1'), + )), + ('ARABIC LIGATURE MEEM WITH YEH', ( + '\u0645\u064A', ('\uFC4A', '', '', ''), + )), + ('ARABIC LIGATURE NOON WITH ALEF MAKSURA', ( + '\u0646\u0649', ('\uFC4F', '', '', '\uFC8E'), + )), + ('ARABIC LIGATURE NOON WITH HAH', ( + '\u0646\u062D', ('\uFC4C', '\uFCD3', '', ''), + )), + ('ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA', ( + '\u0646\u062D\u0649', ('', '', '', '\uFD96'), + )), + ('ARABIC LIGATURE NOON WITH HAH WITH MEEM', ( + '\u0646\u062D\u0645', ('', '\uFD95', '', ''), + )), + ('ARABIC LIGATURE NOON WITH HAH WITH YEH', ( + '\u0646\u062D\u064A', ('', '', '', '\uFDB3'), + )), + ('ARABIC LIGATURE NOON WITH HEH', ( + '\u0646\u0647', ('', '\uFCD6', '\uFCEF', ''), + )), + ('ARABIC LIGATURE NOON WITH JEEM', ( + '\u0646\u062C', ('\uFC4B', '\uFCD2', '', ''), + )), + ('ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA', ( + '\u0646\u062C\u0649', ('', '', '', '\uFD99'), + )), + ('ARABIC LIGATURE NOON WITH JEEM WITH HAH', ( + '\u0646\u062C\u062D', ('', '\uFDB8', '', '\uFDBD'), + )), + ('ARABIC LIGATURE NOON WITH JEEM WITH MEEM', ( + '\u0646\u062C\u0645', ('', '\uFD98', '', '\uFD97'), + )), + ('ARABIC LIGATURE NOON WITH JEEM WITH YEH', ( + '\u0646\u062C\u064A', ('', '', '', '\uFDC7'), + )), + ('ARABIC LIGATURE NOON WITH KHAH', ( + '\u0646\u062E', ('\uFC4D', '\uFCD4', '', ''), + )), + ('ARABIC LIGATURE NOON WITH MEEM', ( + '\u0646\u0645', ('\uFC4E', '\uFCD5', '\uFCEE', '\uFC8C'), + )), + ('ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA', ( + '\u0646\u0645\u0649', ('', '', '', '\uFD9B'), + )), + ('ARABIC LIGATURE NOON WITH MEEM WITH YEH', ( + '\u0646\u0645\u064A', ('', '', '', '\uFD9A'), + )), + ('ARABIC LIGATURE NOON WITH NOON', ( + '\u0646\u0646', ('', '', '', '\uFC8D'), + )), + ('ARABIC LIGATURE NOON WITH REH', ( + '\u0646\u0631', ('', '', '', '\uFC8A'), + )), + ('ARABIC LIGATURE NOON WITH YEH', ( + '\u0646\u064A', ('\uFC50', '', '', '\uFC8F'), + )), + ('ARABIC LIGATURE NOON WITH ZAIN', ( + '\u0646\u0632', ('', '', '', '\uFC8B'), + )), + ('ARABIC LIGATURE QAF WITH ALEF MAKSURA', ( + '\u0642\u0649', ('\uFC35', '', '', '\uFC7E'), + )), + ('ARABIC LIGATURE QAF WITH HAH', ( + '\u0642\u062D', ('\uFC33', '\uFCC2', '', ''), + )), + ('ARABIC LIGATURE QAF WITH MEEM', ( + '\u0642\u0645', ('\uFC34', '\uFCC3', '', ''), + )), + ('ARABIC LIGATURE QAF WITH MEEM WITH HAH', ( + '\u0642\u0645\u062D', ('', '\uFDB4', '', '\uFD7E'), + )), + ('ARABIC LIGATURE QAF WITH MEEM WITH MEEM', ( + '\u0642\u0645\u0645', ('', '', '', '\uFD7F'), + )), + ('ARABIC LIGATURE QAF WITH MEEM WITH YEH', ( + '\u0642\u0645\u064A', ('', '', '', '\uFDB2'), + )), + ('ARABIC LIGATURE QAF WITH YEH', ( + '\u0642\u064A', ('\uFC36', '', '', '\uFC7F'), + )), + ('ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN', ( + '\u0642\u0644\u06D2', ('\uFDF1', '', '', ''), + )), + ('ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF', ( + '\u0631\u0670', ('\uFC5C', '', '', ''), + )), + ('ARABIC LIGATURE SAD WITH ALEF MAKSURA', ( + '\u0635\u0649', ('\uFD05', '', '', '\uFD21'), + )), + ('ARABIC LIGATURE SAD WITH HAH', ( + '\u0635\u062D', ('\uFC20', '\uFCB1', '', ''), + )), + ('ARABIC LIGATURE SAD WITH HAH WITH HAH', ( + '\u0635\u062D\u062D', ('', '\uFD65', '', '\uFD64'), + )), + ('ARABIC LIGATURE SAD WITH HAH WITH YEH', ( + '\u0635\u062D\u064A', ('', '', '', '\uFDA9'), + )), + ('ARABIC LIGATURE SAD WITH KHAH', ( + '\u0635\u062E', ('', '\uFCB2', '', ''), + )), + ('ARABIC LIGATURE SAD WITH MEEM', ( + '\u0635\u0645', ('\uFC21', '\uFCB3', '', ''), + )), + ('ARABIC LIGATURE SAD WITH MEEM WITH MEEM', ( + '\u0635\u0645\u0645', ('', '\uFDC5', '', '\uFD66'), + )), + ('ARABIC LIGATURE SAD WITH REH', ( + '\u0635\u0631', ('\uFD0F', '', '', '\uFD2B'), + )), + ('ARABIC LIGATURE SAD WITH YEH', ( + '\u0635\u064A', ('\uFD06', '', '', '\uFD22'), + )), + ('ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN', ( + '\u0635\u0644\u06D2', ('\uFDF0', '', '', ''), + )), + ('ARABIC LIGATURE SEEN WITH ALEF MAKSURA', ( + '\u0633\u0649', ('\uFCFB', '', '', '\uFD17'), + )), + ('ARABIC LIGATURE SEEN WITH HAH', ( + '\u0633\u062D', ('\uFC1D', '\uFCAE', '\uFD35', ''), + )), + ('ARABIC LIGATURE SEEN WITH HAH WITH JEEM', ( + '\u0633\u062D\u062C', ('', '\uFD5C', '', ''), + )), + ('ARABIC LIGATURE SEEN WITH HEH', ( + '\u0633\u0647', ('', '\uFD31', '\uFCE8', ''), + )), + ('ARABIC LIGATURE SEEN WITH JEEM', ( + '\u0633\u062C', ('\uFC1C', '\uFCAD', '\uFD34', ''), + )), + ('ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA', ( + '\u0633\u062C\u0649', ('', '', '', '\uFD5E'), + )), + ('ARABIC LIGATURE SEEN WITH JEEM WITH HAH', ( + '\u0633\u062C\u062D', ('', '\uFD5D', '', ''), + )), + ('ARABIC LIGATURE SEEN WITH KHAH', ( + '\u0633\u062E', ('\uFC1E', '\uFCAF', '\uFD36', ''), + )), + ('ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA', ( + '\u0633\u062E\u0649', ('', '', '', '\uFDA8'), + )), + ('ARABIC LIGATURE SEEN WITH KHAH WITH YEH', ( + '\u0633\u062E\u064A', ('', '', '', '\uFDC6'), + )), + ('ARABIC LIGATURE SEEN WITH MEEM', ( + '\u0633\u0645', ('\uFC1F', '\uFCB0', '\uFCE7', ''), + )), + ('ARABIC LIGATURE SEEN WITH MEEM WITH HAH', ( + '\u0633\u0645\u062D', ('', '\uFD60', '', '\uFD5F'), + )), + ('ARABIC LIGATURE SEEN WITH MEEM WITH JEEM', ( + '\u0633\u0645\u062C', ('', '\uFD61', '', ''), + )), + ('ARABIC LIGATURE SEEN WITH MEEM WITH MEEM', ( + '\u0633\u0645\u0645', ('', '\uFD63', '', '\uFD62'), + )), + ('ARABIC LIGATURE SEEN WITH REH', ( + '\u0633\u0631', ('\uFD0E', '', '', '\uFD2A'), + )), + ('ARABIC LIGATURE SEEN WITH YEH', ( + '\u0633\u064A', ('\uFCFC', '', '', '\uFD18'), + )), + + # Arabic ligatures with Shadda, the order of characters doesn't matter + ('ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM', ( + '(?:\u064C\u0651|\u0651\u064C)', + + ('\uFC5E', '\uFC5E', '\uFC5E', '\uFC5E'), + )), + ('ARABIC LIGATURE SHADDA WITH KASRATAN ISOLATED FORM', ( + '(?:\u064D\u0651|\u0651\u064D)', + + ('\uFC5F', '\uFC5F', '\uFC5F', '\uFC5F'), + )), + ('ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM', ( + '(?:\u064E\u0651|\u0651\u064E)', + + ('\uFC60', '\uFC60', '\uFC60', '\uFC60'), + )), + ('ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM', ( + '(?:\u064F\u0651|\u0651\u064F)', + + ('\uFC61', '\uFC61', '\uFC61', '\uFC61'), + )), + ('ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM', ( + '(?:\u0650\u0651|\u0651\u0650)', + + ('\uFC62', '\uFC62', '\uFC62', '\uFC62'), + )), + ('ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF', ( + '(?:\u0651\u0670|\u0670\u0651)', ('\uFC63', '', '', ''), + )), + + # There is a special case when they are with Tatweel + ('ARABIC LIGATURE SHADDA WITH FATHA MEDIAL FORM', ( + '\u0640(?:\u064E\u0651|\u0651\u064E)', + + ('\uFCF2', '\uFCF2', '\uFCF2', '\uFCF2'), + )), + ('ARABIC LIGATURE SHADDA WITH DAMMA MEDIAL FORM', ( + '\u0640(?:\u064F\u0651|\u0651\u064F)', + + ('\uFCF3', '\uFCF3', '\uFCF3', '\uFCF3'), + )), + ('ARABIC LIGATURE SHADDA WITH KASRA MEDIAL FORM', ( + '\u0640(?:\u0650\u0651|\u0651\u0650)', + + ('\uFCF4', '\uFCF4', '\uFCF4', '\uFCF4'), + )), + + # Repeated with different keys to be backward compatible + ('ARABIC LIGATURE SHADDA WITH FATHA', ( + '\u0640(?:\u064E\u0651|\u0651\u064E)', + + ('\uFCF2', '\uFCF2', '\uFCF2', '\uFCF2'), + )), + ('ARABIC LIGATURE SHADDA WITH DAMMA', ( + '\u0640(?:\u064F\u0651|\u0651\u064F)', + + ('\uFCF3', '\uFCF3', '\uFCF3', '\uFCF3'), + )), + ('ARABIC LIGATURE SHADDA WITH KASRA', ( + '\u0640(?:\u0650\u0651|\u0651\u0650)', + + ('\uFCF4', '\uFCF4', '\uFCF4', '\uFCF4'), + )), + + ('ARABIC LIGATURE SHEEN WITH ALEF MAKSURA', ( + '\u0634\u0649', ('\uFCFD', '', '', '\uFD19'), + )), + ('ARABIC LIGATURE SHEEN WITH HAH', ( + '\u0634\u062D', ('\uFD0A', '\uFD2E', '\uFD38', '\uFD26'), + )), + ('ARABIC LIGATURE SHEEN WITH HAH WITH MEEM', ( + '\u0634\u062D\u0645', ('', '\uFD68', '', '\uFD67'), + )), + ('ARABIC LIGATURE SHEEN WITH HAH WITH YEH', ( + '\u0634\u062D\u064A', ('', '', '', '\uFDAA'), + )), + ('ARABIC LIGATURE SHEEN WITH HEH', ( + '\u0634\u0647', ('', '\uFD32', '\uFCEA', ''), + )), + ('ARABIC LIGATURE SHEEN WITH JEEM', ( + '\u0634\u062C', ('\uFD09', '\uFD2D', '\uFD37', '\uFD25'), + )), + ('ARABIC LIGATURE SHEEN WITH JEEM WITH YEH', ( + '\u0634\u062C\u064A', ('', '', '', '\uFD69'), + )), + ('ARABIC LIGATURE SHEEN WITH KHAH', ( + '\u0634\u062E', ('\uFD0B', '\uFD2F', '\uFD39', '\uFD27'), + )), + ('ARABIC LIGATURE SHEEN WITH MEEM', ( + '\u0634\u0645', ('\uFD0C', '\uFD30', '\uFCE9', '\uFD28'), + )), + ('ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH', ( + '\u0634\u0645\u062E', ('', '\uFD6B', '', '\uFD6A'), + )), + ('ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM', ( + '\u0634\u0645\u0645', ('', '\uFD6D', '', '\uFD6C'), + )), + ('ARABIC LIGATURE SHEEN WITH REH', ( + '\u0634\u0631', ('\uFD0D', '', '', '\uFD29'), + )), + ('ARABIC LIGATURE SHEEN WITH YEH', ( + '\u0634\u064A', ('\uFCFE', '', '', '\uFD1A'), + )), + ('ARABIC LIGATURE TAH WITH ALEF MAKSURA', ( + '\u0637\u0649', ('\uFCF5', '', '', '\uFD11'), + )), + ('ARABIC LIGATURE TAH WITH HAH', ( + '\u0637\u062D', ('\uFC26', '\uFCB8', '', ''), + )), + ('ARABIC LIGATURE TAH WITH MEEM', ( + '\u0637\u0645', ('\uFC27', '\uFD33', '\uFD3A', ''), + )), + ('ARABIC LIGATURE TAH WITH MEEM WITH HAH', ( + '\u0637\u0645\u062D', ('', '\uFD72', '', '\uFD71'), + )), + ('ARABIC LIGATURE TAH WITH MEEM WITH MEEM', ( + '\u0637\u0645\u0645', ('', '\uFD73', '', ''), + )), + ('ARABIC LIGATURE TAH WITH MEEM WITH YEH', ( + '\u0637\u0645\u064A', ('', '', '', '\uFD74'), + )), + ('ARABIC LIGATURE TAH WITH YEH', ( + '\u0637\u064A', ('\uFCF6', '', '', '\uFD12'), + )), + ('ARABIC LIGATURE TEH WITH ALEF MAKSURA', ( + '\u062A\u0649', ('\uFC0F', '', '', '\uFC74'), + )), + ('ARABIC LIGATURE TEH WITH HAH', ( + '\u062A\u062D', ('\uFC0C', '\uFCA2', '', ''), + )), + ('ARABIC LIGATURE TEH WITH HAH WITH JEEM', ( + '\u062A\u062D\u062C', ('', '\uFD52', '', '\uFD51'), + )), + ('ARABIC LIGATURE TEH WITH HAH WITH MEEM', ( + '\u062A\u062D\u0645', ('', '\uFD53', '', ''), + )), + ('ARABIC LIGATURE TEH WITH HEH', ( + '\u062A\u0647', ('', '\uFCA5', '\uFCE4', ''), + )), + ('ARABIC LIGATURE TEH WITH JEEM', ( + '\u062A\u062C', ('\uFC0B', '\uFCA1', '', ''), + )), + ('ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA', ( + '\u062A\u062C\u0649', ('', '', '', '\uFDA0'), + )), + ('ARABIC LIGATURE TEH WITH JEEM WITH MEEM', ( + '\u062A\u062C\u0645', ('', '\uFD50', '', ''), + )), + ('ARABIC LIGATURE TEH WITH JEEM WITH YEH', ( + '\u062A\u062C\u064A', ('', '', '', '\uFD9F'), + )), + ('ARABIC LIGATURE TEH WITH KHAH', ( + '\u062A\u062E', ('\uFC0D', '\uFCA3', '', ''), + )), + ('ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA', ( + '\u062A\u062E\u0649', ('', '', '', '\uFDA2'), + )), + ('ARABIC LIGATURE TEH WITH KHAH WITH MEEM', ( + '\u062A\u062E\u0645', ('', '\uFD54', '', ''), + )), + ('ARABIC LIGATURE TEH WITH KHAH WITH YEH', ( + '\u062A\u062E\u064A', ('', '', '', '\uFDA1'), + )), + ('ARABIC LIGATURE TEH WITH MEEM', ( + '\u062A\u0645', ('\uFC0E', '\uFCA4', '\uFCE3', '\uFC72'), + )), + ('ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA', ( + '\u062A\u0645\u0649', ('', '', '', '\uFDA4'), + )), + ('ARABIC LIGATURE TEH WITH MEEM WITH HAH', ( + '\u062A\u0645\u062D', ('', '\uFD56', '', ''), + )), + ('ARABIC LIGATURE TEH WITH MEEM WITH JEEM', ( + '\u062A\u0645\u062C', ('', '\uFD55', '', ''), + )), + ('ARABIC LIGATURE TEH WITH MEEM WITH KHAH', ( + '\u062A\u0645\u062E', ('', '\uFD57', '', ''), + )), + ('ARABIC LIGATURE TEH WITH MEEM WITH YEH', ( + '\u062A\u0645\u064A', ('', '', '', '\uFDA3'), + )), + ('ARABIC LIGATURE TEH WITH NOON', ( + '\u062A\u0646', ('', '', '', '\uFC73'), + )), + ('ARABIC LIGATURE TEH WITH REH', ( + '\u062A\u0631', ('', '', '', '\uFC70'), + )), + ('ARABIC LIGATURE TEH WITH YEH', ( + '\u062A\u064A', ('\uFC10', '', '', '\uFC75'), + )), + ('ARABIC LIGATURE TEH WITH ZAIN', ( + '\u062A\u0632', ('', '', '', '\uFC71'), + )), + ('ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF', ( + '\u0630\u0670', ('\uFC5B', '', '', ''), + )), + ('ARABIC LIGATURE THEH WITH ALEF MAKSURA', ( + '\u062B\u0649', ('\uFC13', '', '', '\uFC7A'), + )), + ('ARABIC LIGATURE THEH WITH HEH', ( + '\u062B\u0647', ('', '', '\uFCE6', ''), + )), + ('ARABIC LIGATURE THEH WITH JEEM', ( + '\u062B\u062C', ('\uFC11', '', '', ''), + )), + ('ARABIC LIGATURE THEH WITH MEEM', ( + '\u062B\u0645', ('\uFC12', '\uFCA6', '\uFCE5', '\uFC78'), + )), + ('ARABIC LIGATURE THEH WITH NOON', ( + '\u062B\u0646', ('', '', '', '\uFC79'), + )), + ('ARABIC LIGATURE THEH WITH REH', ( + '\u062B\u0631', ('', '', '', '\uFC76'), + )), + ('ARABIC LIGATURE THEH WITH YEH', ( + '\u062B\u064A', ('\uFC14', '', '', '\uFC7B'), + )), + ('ARABIC LIGATURE THEH WITH ZAIN', ( + '\u062B\u0632', ('', '', '', '\uFC77'), + )), + ('ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA', ( + '\u0626\u0649', ('\uFBF9', '\uFBFB', '', '\uFBFA'), + )), + ('ARABIC LIGATURE YEH WITH ALEF MAKSURA', ( + '\u064A\u0649', ('\uFC59', '', '', '\uFC95'), + )), + ('ARABIC LIGATURE YEH WITH HAH', ( + '\u064A\u062D', ('\uFC56', '\uFCDB', '', ''), + )), + ('ARABIC LIGATURE YEH WITH HAH WITH YEH', ( + '\u064A\u062D\u064A', ('', '', '', '\uFDAE'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE', ( + '\u0626\u06D5', ('\uFBEC', '', '', '\uFBED'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF', ( + '\u0626\u0627', ('\uFBEA', '', '', '\uFBEB'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA', ( + '\u0626\u0649', ('\uFC03', '', '', '\uFC68'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E', ( + '\u0626\u06D0', ('\uFBF6', '\uFBF8', '', '\uFBF7'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH', ( + '\u0626\u062D', ('\uFC01', '\uFC98', '', ''), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH', ( + '\u0626\u0647', ('', '\uFC9B', '\uFCE0', ''), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM', ( + '\u0626\u062C', ('\uFC00', '\uFC97', '', ''), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH', ( + '\u0626\u062E', ('', '\uFC99', '', ''), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM', ( + '\u0626\u0645', ('\uFC02', '\uFC9A', '\uFCDF', '\uFC66'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON', ( + '\u0626\u0646', ('', '', '', '\uFC67'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE', ( + '\u0626\u06C6', ('\uFBF2', '', '', '\uFBF3'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH', ( + '\u0626\u0631', ('', '', '', '\uFC64'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U', ( + '\u0626\u06C7', ('\uFBF0', '', '', '\uFBF1'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW', ( + '\u0626\u0648', ('\uFBEE', '', '', '\uFBEF'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH', ( + '\u0626\u064A', ('\uFC04', '', '', '\uFC69'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU', ( + '\u0626\u06C8', ('\uFBF4', '', '', '\uFBF5'), + )), + ('ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN', ( + '\u0626\u0632', ('', '', '', '\uFC65'), + )), + ('ARABIC LIGATURE YEH WITH HEH', ( + '\u064A\u0647', ('', '\uFCDE', '\uFCF1', ''), + )), + ('ARABIC LIGATURE YEH WITH JEEM', ( + '\u064A\u062C', ('\uFC55', '\uFCDA', '', ''), + )), + ('ARABIC LIGATURE YEH WITH JEEM WITH YEH', ( + '\u064A\u062C\u064A', ('', '', '', '\uFDAF'), + )), + ('ARABIC LIGATURE YEH WITH KHAH', ( + '\u064A\u062E', ('\uFC57', '\uFCDC', '', ''), + )), + ('ARABIC LIGATURE YEH WITH MEEM', ( + '\u064A\u0645', ('\uFC58', '\uFCDD', '\uFCF0', '\uFC93'), + )), + ('ARABIC LIGATURE YEH WITH MEEM WITH MEEM', ( + '\u064A\u0645\u0645', ('', '\uFD9D', '', '\uFD9C'), + )), + ('ARABIC LIGATURE YEH WITH MEEM WITH YEH', ( + '\u064A\u0645\u064A', ('', '', '', '\uFDB0'), + )), + ('ARABIC LIGATURE YEH WITH NOON', ( + '\u064A\u0646', ('', '', '', '\uFC94'), + )), + ('ARABIC LIGATURE YEH WITH REH', ( + '\u064A\u0631', ('', '', '', '\uFC91'), + )), + ('ARABIC LIGATURE YEH WITH YEH', ( + '\u064A\u064A', ('\uFC5A', '', '', '\uFC96'), + )), + ('ARABIC LIGATURE YEH WITH ZAIN', ( + '\u064A\u0632', ('', '', '', '\uFC92'), + )), + ('ARABIC LIGATURE ZAH WITH MEEM', ( + '\u0638\u0645', ('\uFC28', '\uFCB9', '\uFD3B', ''), + )), +) + +LIGATURES = tuple(chain(SENTENCES_LIGATURES, WORDS_LIGATURES, LETTERS_LIGATURES)) diff --git a/exe_generator/arabic_reshaper/reshaper_config.py b/exe_generator/arabic_reshaper/reshaper_config.py new file mode 100644 index 0000000..22e1f23 --- /dev/null +++ b/exe_generator/arabic_reshaper/reshaper_config.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# This work is licensed under the MIT License. +# To view a copy of this license, visit https://opensource.org/licenses/MIT + +# Written by Abdullah Diab (mpcabd) +# Email: mpcabd@gmail.com +# Website: http://mpcabd.xyz + +from __future__ import unicode_literals + +import os + +from configparser import ConfigParser +from pkg_resources import resource_filename + +from .letters import (UNSHAPED, ISOLATED, LETTERS_ARABIC) +from .ligatures import (SENTENCES_LIGATURES, + WORDS_LIGATURES, + LETTERS_LIGATURES) + +try: + from fontTools.ttLib import TTFont + with_font_config = True +except ImportError: + with_font_config = False + +ENABLE_NO_LIGATURES = 0b000 +ENABLE_SENTENCES_LIGATURES = 0b001 +ENABLE_WORDS_LIGATURES = 0b010 +ENABLE_LETTERS_LIGATURES = 0b100 +ENABLE_ALL_LIGATURES = 0b111 + + +def auto_config(configuration=None, configuration_file=None): + configuration_files = [ + resource_filename(__name__, 'default-config.ini') + ] + + if not os.path.exists(configuration_files[0]): + raise Exception( + ('Default configuration file {} not found,' + + ' check the module installation.').format( + configuration_files[0], + ) + ) + + loaded_from_envvar = False + + if not configuration_file: + configuration_file = os.getenv( + 'PYTHON_ARABIC_RESHAPER_CONFIGURATION_FILE' + ) + if configuration_file: + loaded_from_envvar = True + + if configuration_file: + if not os.path.exists(configuration_file): + raise Exception( + 'Configuration file {} not found{}.'.format( + configuration_file, + loaded_from_envvar and ( + ' it is set in your environment variable ' + + 'PYTHON_ARABIC_RESHAPER_CONFIGURATION_FILE' + ) or '' + ) + ) + configuration_files.append(configuration_file) + + configuration_parser = ConfigParser() + configuration_parser.read( + configuration_files + ) + + if configuration: + configuration_parser.read_dict({ + 'ArabicReshaper': configuration + }) + + if 'ArabicReshaper' not in configuration_parser: + raise ValueError( + 'Invalid configuration: ' + 'A section with the name ArabicReshaper was not found' + ) + + return configuration_parser['ArabicReshaper'] + + +def config_for_true_type_font(font_file_path, + ligatures_config=ENABLE_ALL_LIGATURES): + if not with_font_config: + raise Exception('fonttools not installed, ' + + 'install it then rerun this.\n' + + '$ pip install arabic-teshaper[with-fonttools]') + if not font_file_path or not os.path.exists(font_file_path): + raise Exception('Invalid path to font file') + ttfont = TTFont(font_file_path) + has_isolated = True + for k, v in LETTERS_ARABIC.items(): + for table in ttfont['cmap'].tables: + if ord(v[ISOLATED]) in table.cmap: + break + else: + has_isolated = False + break + + configuration = { + 'use_unshaped_instead_of_isolated': not has_isolated, + } + + def process_ligatures(ligatures): + for ligature in ligatures: + forms = list(filter(lambda form: form != '', ligature[1][1])) + n = len(forms) + for form in forms: + for table in ttfont['cmap'].tables: + if ord(form) in table.cmap: + n -= 1 + break + configuration[ligature[0]] = (n == 0) + + if ENABLE_SENTENCES_LIGATURES & ligatures_config: + process_ligatures(SENTENCES_LIGATURES) + + if ENABLE_WORDS_LIGATURES & ligatures_config: + process_ligatures(WORDS_LIGATURES) + + if ENABLE_LETTERS_LIGATURES & ligatures_config: + process_ligatures(LETTERS_LIGATURES) + + return configuration diff --git a/exe_generator/generate_exe_windows.bat b/exe_generator/generate_exe_windows.bat new file mode 100644 index 0000000..c4a971f --- /dev/null +++ b/exe_generator/generate_exe_windows.bat @@ -0,0 +1,3 @@ +pyinstaller --noconfirm ../main.py --hidden-import=sklearn.neighbors._typedefs --hidden-import=sklearn.utils._cython_blas --hidden-import=vtkmodules --hidden-import=vtkmodules.qt --hidden-import=vtk.qt.QVTKRenderWindowInteractor --hidden-import=vtkmodules.all --hidden-import=vtkmodules.qt.QVTKRenderWindowInteractor --hidden-import=vtkmodules.util --hidden-import=vtkmodules.util.numpy_support +xcopy arabic_reshaper dist\main\arabic_reshaper /I +tar.exe -a -c -f visualyse-%date%.zip -C dist\main . \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f3fffd2 --- /dev/null +++ b/main.py @@ -0,0 +1,27 @@ +import logging +import os +import time +import traceback +# Removes annoying pyqode logging +pyqode_logger = logging.getLogger('pyqode.qt') +pyqode_logger.setLevel(logging.WARNING) +from model_view import frontend_adapter + +if __name__ == "__main__": + try: + import resource + + resource.setrlimit( + resource.RLIMIT_CORE, + (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) + except ImportError: + pass + os.chdir(os.path.dirname(os.path.abspath(__file__))) + try: + frontend_adapter.FrontEndAdapter.start_program() + except Exception as e: + with open('crash_log.txt', 'a') as f: + f.write(str(e)) + f.write(traceback.format_exc()) + time.sleep(0.5) + raise e diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/analysis/__init__.py b/model/analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/atom_network/__init__.py b/model/atom_network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/atom_network/atom_ring.py b/model/atom_network/atom_ring.py new file mode 100644 index 0000000..155b79b --- /dev/null +++ b/model/atom_network/atom_ring.py @@ -0,0 +1,6 @@ +class AtomRing: + def __init__(self, atoms): + self.atoms = atoms + + def __str__(self): + return "Length: {} \n Atoms {}".format(len(self.atoms), self.atoms) diff --git a/model/atom_network/oxygen_atom.py b/model/atom_network/oxygen_atom.py new file mode 100644 index 0000000..7473401 --- /dev/null +++ b/model/atom_network/oxygen_atom.py @@ -0,0 +1,7 @@ +class OxygenAtom: + def __init__(self, position, network_nodes): + self.position = position + self.network_nodes = network_nodes + + def __str__(self): + return "Position: {} \n Network Nodes: {}".format(self.position, self.network_nodes) diff --git a/model/atom_network/silicon_atom.py b/model/atom_network/silicon_atom.py new file mode 100644 index 0000000..4b9a56f --- /dev/null +++ b/model/atom_network/silicon_atom.py @@ -0,0 +1,7 @@ +class SiliconAtom: + def __init__(self, position, network_nodes): + self.position = position + self.network_nodes = network_nodes + + def __str__(self): + return "Position: {} \n Network Nodes: {}".format(self.position, self.network_nodes) diff --git a/model/atom_network/silicon_oxygen_network.py b/model/atom_network/silicon_oxygen_network.py new file mode 100644 index 0000000..ed0d1a9 --- /dev/null +++ b/model/atom_network/silicon_oxygen_network.py @@ -0,0 +1,252 @@ +import math + +import networkx as nx +import numpy as np + +from model.atom_network.atom_ring import AtomRing +from model.atom_network.oxygen_atom import OxygenAtom +from model.atom_network.silicon_atom import SiliconAtom +from utils import point_utilities + + +class SiliconOxygenNetwork(): + def __init__(self, saasmi_instance): + self._silicon_oxygen_graph = nx.Graph() + self._silicon_graph = nx.Graph() + self._oxygen_graph = nx.Graph() + self.silicon_atoms = None + self.oxygen_atoms = None + self.atom_rings = None + self.generate_network_from_rag(saasmi_instance) + self.generate_graphs() + + @property + def silicon_oxygen_graph(self): + try: + return self._silicon_oxygen_graph + except AttributeError: + self._silicon_oxygen_graph = nx.Graph() + self._silicon_graph = nx.Graph() + self._oxygen_graph = nx.Graph() + self.generate_graphs() + return self._silicon_oxygen_graph + + @property + def network_polygons(self): + try: + return self._network_polygons + except AttributeError: + self._network_polygons = [] + self.generate_graphs() + return self._network_polygons + + @property + def oxygen_graph(self): + try: + return self._oxygen_graph + except AttributeError: + self._silicon_oxygen_graph = nx.Graph() + self._silicon_graph = nx.Graph() + self._oxygen_graph = nx.Graph() + self.generate_graphs() + return self._oxygen_graph + + @property + def silicon_graph(self): + try: + return self._silicon_graph + except AttributeError: + self._silicon_oxygen_graph = nx.Graph() + self._silicon_graph = nx.Graph() + self._oxygen_graph = nx.Graph() + self.generate_graphs() + return self._silicon_graph + + def generate_network_from_rag(self, saasmi_instance): + rag = saasmi_instance.rag + face_cycles = saasmi_instance.get_face_cycles_of_length_n(rag=rag, n=3) + self.silicon_atoms = self.get_silicon_atoms(rag, face_cycles) + self.oxygen_atoms = self.get_oxygen_atoms(rag) + self.atom_rings = self.get_atom_rings(rag=rag, oxygen_atoms=self.oxygen_atoms, silicon_atoms=self.silicon_atoms) + + @staticmethod + def get_silicon_atoms(rag, face_cylces): + silicon_atom_list = [] + for face_cycle in face_cylces: + position = SiliconOxygenNetwork.get_triplet_center(rag, face_cycle) + network_nodes = face_cycle.nodes + if position is not None: + silicon_atom_list.append(SiliconAtom(position=position, network_nodes=network_nodes)) + return silicon_atom_list + + def get_oxygen_atoms(self, rag): + oxygen_atom_list = [] + for edge in rag.edges: + try: + start_positon = rag.nodes[edge[0]]["marker"] + end_position = rag.nodes[edge[1]]["marker"] + oxygen_position = list((np.asarray(start_positon) + np.asarray(end_position)) / 2) + network_nodes = list(edge) + oxygen_atom_list.append(OxygenAtom(position=oxygen_position, network_nodes=network_nodes)) + except KeyError: + pass + return oxygen_atom_list + + @staticmethod + def get_triplet_center(rag, face_cycle): + """Return center of given triplet with ring center attributes.""" + cclist = [rag.nodes(data='marker')[i] for i in list(face_cycle.nodes)] + if cclist[0] is None: + cclist = [rag.nodes(data='marker')[i] for i in list(face_cycle.nodes)] + ccarray = np.asarray(cclist) + try: + triplet_center = [np.mean(ccarray[:, 0]), np.mean(ccarray[:, 1])] + except IndexError: + print(ccarray) + triplet_center = None + return triplet_center + + @staticmethod + def get_atom_rings(rag, oxygen_atoms, silicon_atoms): + atom_rings = [] + for node in rag.nodes: + ring_atoms = [] + for oxygen_atom in oxygen_atoms: + if node in oxygen_atom.network_nodes: + ring_atoms.append(oxygen_atom) + for silicon_atom in silicon_atoms: + if node in silicon_atom.network_nodes: + ring_atoms.append(silicon_atom) + if ring_atoms != []: + atom_rings.append(AtomRing(atoms=ring_atoms)) + return atom_rings + + def add_atom_network_edge(self, start_node, end_node, graph): + angle = self.calc_edge_angle(start_node, end_node) + if angle < 0: + angle += 180 + distance = self.calc_edge_distance(start_node, end_node) + graph.add_edge(start_node, end_node, distance=distance, angle=angle) + + def add_atom_ring_to_graph(self, ring, graph, atom_idenfitier=None): + ring_tuple = [tuple(position) for position in ring] + first_atom = None + prev_atom = None + for position in ring_tuple: + atom_class = atom_idenfitier[position]["atom_type"] + if atom_class == OxygenAtom: + atom_class_str = "oxygen" + else: + atom_class_str = "silicon" + graph.add_node(position, marker=position, atom_type=atom_class_str, + network_nodes=atom_idenfitier[position]["network_nodes"]) + if first_atom is None: + first_atom = position + prev_atom = position + else: + self.add_atom_network_edge(prev_atom, position, graph=graph) + graph.add_edge(prev_atom, position) + prev_atom = position + if prev_atom != first_atom: + self.add_atom_network_edge(prev_atom, first_atom, graph=graph) + + def generate_graphs(self): + for ring in self.atom_rings: + oxygen_ring_positions = [] + silicon_ring_positions = [] + oxygen_silicon_positions = [] + atom_identification = {} + for atom in ring.atoms: + if isinstance(atom, OxygenAtom): + oxygen_ring_positions.append(atom.position) + if isinstance(atom, SiliconAtom): + silicon_ring_positions.append(atom.position) + oxygen_silicon_positions.append(atom.position) + atom_identification[tuple(atom.position)] = {"atom_type": type(atom), + "network_nodes": atom.network_nodes} + oxygen_silicon_positions = point_utilities.sort_points_counter_clockwise(points=oxygen_silicon_positions) + silicon_ring_positions = point_utilities.sort_points_counter_clockwise(points=silicon_ring_positions) + oxygen_ring_positions = point_utilities.sort_points_counter_clockwise(points=oxygen_ring_positions) + + self.add_atom_ring_to_graph(oxygen_silicon_positions, self.silicon_oxygen_graph, + atom_idenfitier=atom_identification) + self.add_atom_ring_to_graph(oxygen_ring_positions, self.oxygen_graph, atom_idenfitier=atom_identification) + self.add_atom_ring_to_graph(silicon_ring_positions, self.silicon_graph, atom_idenfitier=atom_identification) + self.add_network_polygon(silicon_ring_positions) + + def calc_edge_angle(self, start_node, end_node): + x = end_node[0] - start_node[0] + y = end_node[1] - start_node[1] + angle = math.atan2(y, x) + angle = math.degrees(angle) + return angle + + def calc_edge_distance(self, start_node, end_node): + distance = math.hypot(end_node[0] - start_node[0], + end_node[1] - start_node[1]) + return distance + + def get_network_distances(self, atom_type=None, selected_node_ids=None): + network_distances = [] + if atom_type == "silicon": + graph = self.silicon_graph + elif atom_type == "oxygen": + graph = self.oxygen_graph + else: + graph = self.silicon_oxygen_graph + for edge in graph.edges: + if selected_node_ids is not None and not [node for node in edge if node in selected_node_ids]: + continue + network_distances.append(graph.edges[edge]['distance']) + return network_distances + + def get_network_angles(self, atom_type=None, selected_node_ids=None): + network_angles = [] + if atom_type == "silicon": + graph = self.silicon_graph + elif atom_type == "oxygen": + graph = self.oxygen_graph + else: + graph = self.silicon_oxygen_graph + for edge in graph.edges: + if selected_node_ids is not None and not [node for node in edge if node in selected_node_ids]: + continue + network_angles.append(graph.edges[edge]['angle']) + return network_angles + + @staticmethod + def __get_angle(a, b, c): + """stolen from https://medium.com/@manivannan_data/find-the-angle-between-three-points-from-2d-using-python-348c513e2cd""" + ang = math.degrees(math.atan2(c[1] - b[1], c[0] - b[0]) - math.atan2(a[1] - b[1], a[0] - b[0])) + return 180 - abs(ang) + + def _calc_angle_between_atoms(self, atom_triangle_positions): + position_list = [np.array(position) for position in atom_triangle_positions] + return self.__get_angle(*position_list) + + def _calc_atom_triangle_from_oxygen_edges(self, edges): + atom_triangle_positions = [] + if len(edges) != 2: + return None + for edge in edges: + for node in edge: + if node not in atom_triangle_positions: + atom_triangle_positions.append(node) + + atom_triangle_angle = self._calc_angle_between_atoms(atom_triangle_positions) + return atom_triangle_angle + + def get_atom_triangle_angle(self, selected_node_ids=None): + atom_triangle_angle_list = [] + for node in self.silicon_oxygen_graph.nodes: + if selected_node_ids is not None and node not in selected_node_ids: + continue + if self.silicon_oxygen_graph.nodes[node]["atom_type"] == "oxygen": + edges = self.silicon_oxygen_graph.edges(node) + angle = self._calc_atom_triangle_from_oxygen_edges(edges) + if angle is not None: + atom_triangle_angle_list.append(angle) + return atom_triangle_angle_list + + def add_network_polygon(self, silicon_ring_positions): + self.network_polygons.append(silicon_ring_positions) diff --git a/model/config.py b/model/config.py new file mode 100644 index 0000000..cd64804 --- /dev/null +++ b/model/config.py @@ -0,0 +1,284 @@ +import copy +import logging +import os +import pprint + +import yaml + +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M') +mpl_logger = logging.getLogger('matplotlib') +mpl_logger.setLevel(logging.WARNING) +log = logging.getLogger(__name__) + +default_color_map_dict = {"value_min": 0, + "value_max": 100, + "use_bound_colors": False, + "color_lower_bound": [0, 0, 0], + "color_upper_bound": [0, 0, 0], + "color_map_name": "Greys", + } + +default_config = {} +default_config["time_stamps"] = {"digits_before": 4, + "digits_after": 4, + "font_size": 18, + "color": {"red": 0, "green": 0, "blue": 0, }, + "unit": "seconds", + "show_time_stamps": True, + "show_scan_direction": True, + "position": [10, 10], + "start_time": 0, + "pre_text": "Time Stamp", + } +default_config['render_window'] = {"background_color": [0, 0, 0, 0], + "nan_color": [0, 0, 0, 0], + } +default_config['import_h5'] = {"file_path": None, + "number_of_bins": 100, + "scan_directions": [], + "visualyse_window": "full", + "import_images": [0, 0], + "cut_zoom": True, + "remove_offset": True, + "to_grid_method": "histogram", + "use_first_image_xy_values": False, + "use_ideal_xy_values": False, + "use_4ths_column": False, + } + +default_config['import_npy'] = {"file_path": None, + "number_of_bins": 100, + "visualyse_window": "full", + "to_grid_method": "histogram", + } + +default_config['import_gwy'] = {"file_path": None, + "channel": None, } + +default_config['import_image'] = {"file_path": None} +default_config['import_arn'] = {"file_path": None} +default_config['import_sxm'] = {"file_path": None} +default_config['import_avi'] = {"file_path": None} +default_config['import_vsy'] = {"file_path": None} +default_config['import_stp'] = {"file_path": None} +default_config['import_feature'] = {"file_path": None, + "feature_ratio": [1, 1], + } +default_config['import_filter_log_file'] = {"file_path": None} +default_config['program_settings'] = {"filter_1d_multiprocessing": False, + "filter_1d_concurrency": 10, + "filter_2d_multiprocessing": False, + "filter_2d_concurrency": 10, + 'show_second_window': False, + 'main_render_window': 'VTK', + 'second_render_window': 'VTK', + '2d_filter_image': 'VTK', + '2d_filter_fft': 'VTK', + '1d_filter_image': 'VTK', + '1d_filter_signal': 'VTK', + '1d_filter_fft': 'VTK', + 'matplotlib_toolbar': False, + 'auto_save': False, + 'auto_save_interval': 30, + } +default_config['feature_settings'] = {"chosen_feature_class": None, + "feature_point_size": 1, + "feature_detector": None, + "auto_detect_feature_distance": 3, + "feature_geometry": "Disk", + "feature_thickness_percent": 10, + "highlight_method": "complementary", + "highlight_brightness": 0.2, + "highlight_color": [255, 255, 255, 255], + "feature_detector_parameters": {"min_sigma": 2, + "max_sigma": 2, + "num_sigma": 10, + "threshold": 0.2, + "overlap": 0, + "detect_maxima": True, } + } + +default_config['yolo_settings'] = {"yolo_path": "", + "weight_path": "", + "save_path": "training_data", + "number_of_epochs": 100, + "batch_size": 16, + "name": "", + "additional_parameters": "", + } + +default_config['feature_grid_settings'] = {"visualization_settings": {"color": [125, 125, 125, 125], + "point_size": 3, + "function": "Average", }, + "detection_settings": {"min_sigma": 2, + "max_sigma": 2, + "num_sigma": 10, + "threshold": 0.2, + "overlap": 0, + "detect_maxima": True, } + } + +default_config['feature_tracker'] = {'feature_class_ concretization': [], + 'auto__fill': True, + 'current_feature_type': None} + +default_config['export_video'] = {"file_path": None, + "start_frame": None, + "end_frame": None, + "frames_per_second": 1, + "resolution": [500, 500], + "file_format": '.ogv', + "background_color": [0, 0, 0, 255], + "show_rag": False, + "show_features": False, + "show_scale": False, } + +default_config['export_image'] = {"file_path": None, + "frame": None, + "resolution": [500, 500], + "file_format": '.png', + "background_color": [0, 0, 0, 255], + "show_rag": False, + "show_features": False, + "render_backend": "vtk", + "show_scale": False, + } + +default_config['export_gwy'] = {"file_path": None, + "frame_number": None, + "image_title_raw": None, + "image_title_filtered": None, } +default_config['export_xyz'] = {"file_path": None, + "comment": "", + "x_stretch_factor": 1, + "y_stretch_factor": 1, } + +default_config['export_as_zip'] = {'export_path': None, + 'export_video': True, + 'export_video_frames_per_second': 20, + 'export_video_resolution': [500, 500], + 'export_video_file_format': ".ogv", + 'export_video_background_color': [0, 0, 0, 255], + 'export_vsy': True, + 'export_original': True, + 'export_config': True, + 'export_filter_log': True, + 'export_feature_list': True, + 'export_images': True, + 'export_images_resolution': [500, 500], + 'export_images_file_format': ".png", + 'export_images_background_color': [0, 0, 0, 255], + } + +default_config['export_as_vsy'] = {'export_path': None, } + +default_config['saasmi'] = {"edge_radius": 3, + "rag_edge_color_selector": "distance", + "rag_angle_color_map_dict": copy.copy(default_color_map_dict), + "rag_distance_color_map_dict": copy.copy(default_color_map_dict), + "node_color_map": "Dark2", + "node_radius": 7, + "k-neighbors": 30, + "beta": 250, + "disk_size": 10, + "auto_generate_rag": True, + "border_color": [50, 50, 50, 255], + "rag_color_map": "Dark2", + "resolution_image": [500, 500], + "file_path_image": "test.png", + "opacity": 100, + "auto_update": True, + "network_atom_1_radius": 1, + "network_atom_1_color": [255, 0, 0], + "network_atom_2_radius": 1, + "network_atom_2_color": [0, 255, 0], + "network_ring_thickness": 0.5, + "network_angle_color_map_dict": copy.copy(default_color_map_dict), + "network_distance_color_map_dict": copy.copy(default_color_map_dict), + "network_show_network_structure": False, + "network_show_network_polygons": False, + "network_show_atoms": False, + "show_graph": True, + "network_ring_type": "oxygen_silicon", + "network_edge_color_selector": "distance", + "visualization_backend": None, + "image_mask_file_path": "", + "export_csv_file_path": None, + } +default_config["measuring_points_dialog"] = {"image_show": True, + "image_color_map": "gray", + "image_nan_color": [1, 1, 1], + "real_points_show": True, + "ideal_points_show": True, + "real_points_plot_type": "Line", + "real_points_size": 1, + "real_points_color": [0, 0, 0], + "ideal_points_plot_type": "Line", + "ideal_points_size": 1, + "ideal_points_color": [0, 0, 0], } + +default_config['density_dialog'] = {"image_color_map": "gray", + "nan_color": [1, 1, 1]} + +default_config["feature_rag"] = {"export_file_path": None, + } + +default_config["render_point_cloud"] = {"show_points": True, + "z_scale": 1, + "greyscale": False, + "color_map_percentile": [0, 100]} + + +def load_config(): + try: + config_loaded = yaml.safe_load(open("config/config.yaml")) + except FileNotFoundError: + config_loaded = None + if config_loaded is None: + config_loaded = default_config + else: + _validate_loaded_config(config_loaded, default_config) + return config_loaded + + +def force_load_config(config_file_path) -> bool: + global config + try: + config = yaml.safe_load(open(config_file_path)) + dump_config() + return True + except: + return False + + +def _validate_loaded_config(config_loaded, default_config): + for key, value in default_config.items(): + if key not in config_loaded: + config_loaded[key] = default_config[key] + elif isinstance(value, dict): + _validate_loaded_config(config_loaded[key], default_config[key]) + + +def set_value(key, sub_key, value): + config[key][sub_key] = value + dump_config() + + +def get_value(key, sub_key): + return config[key][sub_key] + + +def dump_config(config_file_name=None): + if not os.path.isdir("config"): + os.mkdir("config") + config_file_name = config_file_name or "config/config.yaml" + + if os.path.exists(config_file_name): + os.remove(config_file_name) + yaml.safe_dump(config, open(config_file_name, "w"), default_flow_style=False, sort_keys=False) + + +config = load_config() +log.info("Config was read:\n" + pprint.pformat(config)) +dump_config() diff --git a/model/data/__init__.py b/model/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/data_manipulation/__init__.py b/model/data/data_manipulation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/data_manipulation/data_reconstruction/__init__.py b/model/data/data_manipulation/data_reconstruction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/data_manipulation/data_reconstruction/guassian_probability_density_function.py b/model/data/data_manipulation/data_reconstruction/guassian_probability_density_function.py new file mode 100644 index 0000000..c403f14 --- /dev/null +++ b/model/data/data_manipulation/data_reconstruction/guassian_probability_density_function.py @@ -0,0 +1,5 @@ +import scipy.stats +def apply_guassian_pdf_kernel(array): + kernel = scipy.stats.gaussian_kde(array) + output = kernel(array) + return output \ No newline at end of file diff --git a/model/data/exporter/__init__.py b/model/data/exporter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/exporter/export_scan_zip_file.py b/model/data/exporter/export_scan_zip_file.py new file mode 100644 index 0000000..55d8eba --- /dev/null +++ b/model/data/exporter/export_scan_zip_file.py @@ -0,0 +1,87 @@ +import logging +import os +import shutil +import tempfile + +from model import config + +log = logging.getLogger(__name__) + + +def save_current_scan_in_zip_file(model_view, file_path): + if file_path[-4:] == ".zip": + file_path = file_path[:-4] + elif file_path == "": + file_path = "archive" + dirpath = tempfile.mkdtemp() + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_video'): + video_file_path = os.path.join(dirpath, "video") + video_file_path = _generate_video_for_zip_file(model_view=model_view, video_path=video_file_path) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_filter_log'): + filter_log_file_path = os.path.join(dirpath, "filter_log") + model_view.generate_filter_logs_file(filter_log_file_path) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_feature_list'): + feature_file_path = os.path.join(dirpath, "features") + model_view.save_feature_tracker_in_file(feature_file_path) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_config'): + config_file_path = os.path.join(dirpath, "config") + config.dump_config(config_file_path) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_vsy'): + vsy_file_path = os.path.join(dirpath, "vsy_object") + model_view.save_scan_in_file(vsy_file_path) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_original'): + original_file_path = model_view.get_current_scan().file_path + original_file_name = original_file_path.split('/')[-1] + shutil.copyfile(original_file_path, os.path.join(dirpath, original_file_name)) + + if model_view.get_config_by_key_sub_key('export_as_zip', 'export_images'): + path = os.path.join(dirpath, "Images") + os.mkdir(path) + _generate_images_for_zip_file(model_view=model_view, folder_path=path) + shutil.make_archive(file_path, 'zip', dirpath) + shutil.rmtree(dirpath) + + +def _generate_video_for_zip_file(model_view, video_path): + try: + from view.image_visualization.vtk.rendering_methods import visualize_as_video + except: + log.info("No front-end found to render the video") + return None + background_color = model_view.get_config_by_key_sub_key('export_as_zip', 'export_video_background_color') + start_frame, end_frame = 0, 0 + frames_per_second = model_view.get_config_by_key_sub_key('export_as_zip', 'export_video_frames_per_second') + resolution = model_view.get_config_by_key_sub_key('export_as_zip', 'export_video_resolution') + file_format = model_view.get_config_by_key_sub_key('export_as_zip', 'export_video_file_format') + video_path = visualize_as_video.export_as_video(file_path=video_path, model_view=model_view, + background_color=background_color, + start_frame=start_frame, end_frame=end_frame, + frames_per_second=frames_per_second, + resolution=resolution, file_format=file_format) + return video_path + + +def _generate_images_for_zip_file(model_view, folder_path): + try: + from view.image_visualization.vtk.rendering_methods import visualize_as_image + except: + log.info("No front-end found to render the video") + return None + resolution = model_view.get_config_by_key_sub_key('export_as_zip', 'export_images_resolution') + file_format = model_view.get_config_by_key_sub_key('export_as_zip', 'export_images_file_format') + background_color = model_view.get_config_by_key_sub_key('export_as_zip', 'export_images_background_color') + number_of_images = model_view.get_maximum_image_index_by_string() + 1 + for image_number in range(number_of_images): + file_name = str(image_number) + file_path = os.path.join(folder_path, file_name) + visualize_as_image.export_as_image(file_path=file_path, model_view=model_view, image_number=image_number, + resolution=resolution, file_format=file_format, + background_color=background_color) + + return folder_path diff --git a/model/data/exporter/export_yolo_data.py b/model/data/exporter/export_yolo_data.py new file mode 100644 index 0000000..73f0783 --- /dev/null +++ b/model/data/exporter/export_yolo_data.py @@ -0,0 +1,119 @@ + + +import logging +import os +import shutil +import tempfile +import imageio +import errno + +import yaml +from PIL import Image +import scipy.misc +import numpy as np +from model import config + +log = logging.getLogger(__name__) + + +def export_training_data(model_view, file_path): + if file_path[-4:] == ".zip": + file_path = file_path[:-4] + elif file_path == "": + file_path = "training_data" + dirpath = tempfile.mkdtemp() + generate_training_data(model_view, dirpath) + + shutil.make_archive(file_path, 'zip', dirpath) + shutil.rmtree(dirpath) + + +def generate_annotation_file(model_view, image_number, feature_class_list, image_shape, path_annotation): + current_scan = model_view.get_scan_by_scan_index(None) + feature_organizer = current_scan.feature_organizer_instance + feature_dict = feature_organizer.get_feature_dict(image_number) + annotation_string = "" + for feature_class_name, features in feature_dict.items(): + feature_class_index = feature_class_list.index(feature_class_name) + feature_class_point_position = [] + feature_visualization = model_view.get_feature_class_visualization_representation(feature_class_name) + + if feature_visualization is not None: + feature_radius = feature_visualization['feature_point_size'] + else: + feature_radius = model_view.get_config_by_key_sub_key("feature_settings", "feature_point_size") + for feature_index, feature in features.items(): + for point in feature: + feature_class_point_position.append(point) + for point in feature_class_point_position: + x = point[0] / image_shape[0] + y = point[1] / image_shape[1] + width = feature_radius * 2 / image_shape[0] + height = feature_radius * 2 / image_shape[1] + annotation_string += "{} {} {} {} {}\n".format(feature_class_index, x, y, width, height) + file_name = "image{:06d}.txt".format(image_number) + file_path = os.path.join(path_annotation, file_name) + with open(file_path, "w") as file: + file.write(annotation_string) + + +def generate_classes_file(model_view, path): + feature_classes = model_view.get_feature_classes_list() + classes_string = "" + for feature_class_index, feature_class_name in enumerate(feature_classes): + classes_string += "{} {}\n".format(feature_class_index, feature_class_name) + file_path = os.path.join(path, "classes.txt") + with open(file_path, "w") as file: + file.write(classes_string) + return feature_classes + + +def generate_yolo_yaml(model_view, file_path, image_path): + feature_classes = model_view.get_feature_classes_list() + yolo_dict = {} + yolo_dict['nc'] = len(feature_classes) + yolo_dict['names'] = feature_classes + yolo_dict['train'] = image_path + yolo_dict['val'] = image_path + yaml.dump(yolo_dict, open(file_path, "w"), default_flow_style=True) + + +def generate_jpg(image, image_number, image_path): + image = np.nan_to_num(image) + file_name = "image{:06d}.jpg".format(image_number) + file_path = os.path.join(image_path, file_name) + imageio.imwrite(file_path, image) + + +def generate_training_data(model_view, path): + max_image_number = model_view.get_maximum_image_index() + path_images = os.path.join(path, "images") + yolo_yaml_path = os.path.join(path, 'visualyse.yaml') + save_generate_dir(path_images) + path_annotation = os.path.join(path, "labels") + save_generate_dir(path_annotation) + feature_class_list = generate_classes_file(model_view, path) + generate_yolo_yaml(model_view, yolo_yaml_path, path_images) + for image_number in range(max_image_number + 1): + image = model_view.get_2d_image_by_number(image_number) + generate_jpg(image, image_number, path_images) + generate_annotation_file(model_view, image_number, feature_class_list, image.shape, path_annotation) + + +def generate_detection_data(model_view, path): + max_image_number = model_view.get_maximum_image_index() + path_images = os.path.join(path, "detection_images") + save_generate_dir(path_images) + for image_number in range(max_image_number + 1): + image = model_view.get_2d_image_by_number(image_number) + generate_jpg(image, image_number, path_images) + + +def save_generate_dir(path): + """Stolen from https://stackoverflow.com/questions/12517451/automatically-creating-directories-with-file-output""" + if not os.path.exists(path): + try: + os.makedirs(path) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise diff --git a/model/data/exporter/exporter_gwyddion.py b/model/data/exporter/exporter_gwyddion.py new file mode 100644 index 0000000..ff7d590 --- /dev/null +++ b/model/data/exporter/exporter_gwyddion.py @@ -0,0 +1,28 @@ +import numpy as np +from gwyfile.objects import GwyContainer, GwyDataField + +from model.data import scan_organizier + + +class GwyExporter: + def __init__(self, file_path, frame_number, raw_title, filtered_title): + self.file_path = file_path + self.frame_number = frame_number + self.raw_title = raw_title + self.filtered_title = filtered_title + + def export(self): + current_scan = scan_organizier.ScanOrganizer.current_scan + gwy_container = GwyContainer() + gwy_container['/0/data/title'] = self.filtered_title + data = current_scan.images[self.frame_number] + data = np.nan_to_num(data) + gwy_container['/0/data'] = GwyDataField(data) + gwy_container['/1/data/title'] = self.raw_title + tmp_original_image = current_scan.show_original_image + current_scan.show_original_image = True + data = current_scan.images[self.frame_number] + data = np.nan_to_num(data) + gwy_container['/1/data'] = GwyDataField(data) + current_scan.show_original_image = tmp_original_image + gwy_container.tofile(self.file_path) diff --git a/model/data/exporter/exporter_rtsstem.py b/model/data/exporter/exporter_rtsstem.py new file mode 100644 index 0000000..3d41ff0 --- /dev/null +++ b/model/data/exporter/exporter_rtsstem.py @@ -0,0 +1,25 @@ +from array import array + +import numpy as np + +from model.data import scan_organizier + + +class RtssemExporter: + def __init__(self, image_number, filepath): + self.image = None + self.filepath = filepath + self.image = scan_organizier.ScanOrganizer.current_scan.images[image_number] + self.image = np.nan_to_num(self.image) + isnotnan_mask = ~np.isnan(self.image) + self.output_mask = isnotnan_mask.astype(int) + + def export(self): + self.binary_write(self.image, self.filepath + "_ysparse.dat") + self.binary_write(self.output_mask, self.filepath + "_mask.dat") + + def binary_write(self, arr, output_filename, fmt='f'): + output_file = open(output_filename, 'wb') + float_array = array(fmt, arr.ravel()) + float_array.tofile(output_file) + output_file.close() diff --git a/model/data/feature/__init__.py b/model/data/feature/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/feature/feature_analyser.py b/model/data/feature/feature_analyser.py new file mode 100644 index 0000000..371ccc0 --- /dev/null +++ b/model/data/feature/feature_analyser.py @@ -0,0 +1,201 @@ +import logging +import math + +import numpy as np + +log = logging.getLogger(__name__) + + +class FeatureAnalyser: + def __init__(self, feature_organizer_instance): + self.feature_organizer = feature_organizer_instance + + def __get_feature_list(self, feature_class_index, feature_index): + feature_list = [None] * self.feature_organizer.number_of_images + feature_point_dict = self.feature_organizer.get_feature_points(feature_class_index=feature_class_index, + feature_index=feature_index) + for image_number, points in feature_point_dict.items(): + try: + feature_list[image_number] = points + except IndexError: + pass + return feature_list + + def __get_feature_class_index_by_name(self, feature_class_name): + for feature_class_index, feature_class in enumerate(self.feature_organizer.feature_classes): + if str(feature_class) == feature_class_name: + return feature_class_index + + def __get_feature_class(self, feature_class_index): + return self.feature_organizer.feature_classes[feature_class_index] + + def get_feature_movement_vector(self, image_range, feature_class_index, feature_index): + feature_point_list = self.__get_feature_list(feature_class_index, feature_index) + feature_centroids = self._generate_centroid_for_features(feature_point_list) + feature_movement_vectors = self._generate_continuous_feature_movement_vectors(feature_centroids) + feature_movement_vectors = feature_movement_vectors[image_range[0]:image_range[1] + 1] + return feature_movement_vectors + + def _generate_continuous_feature_movement_vectors(self, centroid_list): + movement_list = list() + for i in range(len(centroid_list)): + if i == 0: + movement_list.append((0, 0)) + else: + x_vector = centroid_list[i][0] - centroid_list[i - 1][0] + y_vector = centroid_list[i][1] - centroid_list[i - 1][1] + movement_list.append((x_vector, y_vector)) + return movement_list + + def _generate_feature_movement_vectors(self, centroid_list): + movement_list = list() + jump_start = None + for image_number in range(len(centroid_list)): + if centroid_list[image_number] == (np.nan, np.nan) and jump_start is None: + movement_list.append([0, 0]) + elif centroid_list[image_number] != (np.nan, np.nan) and jump_start is None: + jump_start = image_number + movement_list.append([0, 0]) + elif centroid_list[image_number] != (np.nan, np.nan) and jump_start is not None: + jump_vector_x = centroid_list[image_number][0] - centroid_list[jump_start][0] + jump_vector_y = centroid_list[image_number][1] - centroid_list[jump_start][1] + for _ in range(jump_start + 1, image_number + 1): + number_of_images = image_number - jump_start + movement_list.append([jump_vector_x / number_of_images, jump_vector_y / number_of_images]) + jump_start = image_number + if jump_start: + for _ in range(len(centroid_list) - len(movement_list)): + movement_list.append([0, 0]) + return movement_list + + def _generate_centroid_for_features(self, feature_point_list): + feature_centroid_list = list() + for image_number, feature_points in enumerate(feature_point_list): + if feature_points is None or len(feature_points) == 0: + feature_centroid_list.append((np.nan, np.nan)) + elif len(feature_points) == 1: + feature_centroid_list.append(feature_points[0]) + elif len(feature_points) > 1: + feature_centroid_list.append(self._generate_centroid(feature_points)) + return feature_centroid_list + + def _generate_centroid(self, feature_points): + x = [p[0] for p in feature_points] + y = [p[1] for p in feature_points] + centroid = (sum(x) / len(feature_points), sum(y) / len(feature_points)) + return centroid + + def get_centroid_list(self, feature_class_index, feature_index): + feature_point_list = self.__get_feature_list(feature_class_index, feature_index) + centroid_list = self._generate_centroid_for_features(feature_point_list) + return centroid_list + + def get_jump_angle_list(self, image_range, feature_class_index, feature_index): + feature_movement_vectors = self.get_feature_movement_vector(image_range=image_range, + feature_class_index=feature_class_index, + feature_index=feature_index) + jump_angle_list = list() + for movement_vector in feature_movement_vectors: + if movement_vector == (np.nan, np.nan): + jump_angle_list.append(np.nan) + elif movement_vector != (0, 0): + jump_angle_list.append(np.degrees(np.arctan2(*movement_vector)) % 360.0) + jump_angle_list = np.deg2rad(jump_angle_list) + return jump_angle_list + + def get_feature_class_movement_list(self, feature_class_index): + feature_class = self.__get_feature_class(feature_class_index) + feature_class_movement_list = list() + for feature_index, feature in enumerate(feature_class.features): + feature_point_list = self.__get_feature_list(feature_class_index, feature_index) + feature_centroids = self._generate_centroid_for_features(feature_point_list) + feature_movement_vectors = self._generate_feature_movement_vectors(feature_centroids) + feature_class_movement_list.append(feature_movement_vectors) + return feature_class_movement_list + + def get_feature_in_image_duration(self, image_range, feature_class_index, feature_index): + feature_point_list = self.__get_feature_list(feature_class_index, feature_index) + feature_centroids = self._generate_centroid_for_features(feature_point_list) + feature_centroids = feature_centroids[image_range[0]:image_range[1] + 1] + feature_duration = 0 + for feature_centroid in feature_centroids: + if not math.isnan(feature_centroid[0]): + feature_duration += 1 + return feature_duration + + def get_feature_jump_list(self, image_range, feature_class_index, feature_index, threshold, count_nan_jumps=True): + feature_movement_vectors = self.get_feature_movement_vector(image_range, feature_class_index, feature_index) + jump_list = [] + prev_jump_vector = (0, 0) + for movement_vector in feature_movement_vectors: + if math.isnan(movement_vector[0]): + if not math.isnan(prev_jump_vector[0]) and count_nan_jumps is True: + jump_list.append(1) + else: + jump_list.append(0) + else: + jump_length = math.sqrt(movement_vector[0] ** 2 + movement_vector[1] ** 2) + if jump_length >= threshold or (count_nan_jumps is True and math.isnan(prev_jump_vector[0])): + jump_list.append(1) + else: + jump_list.append(0) + prev_jump_vector = movement_vector + return jump_list + + def get_feature_class_jump_dict(self, image_range, feature_class_name, feature_list, threshold, + count_nan_jumps=True): + feature_class_index = self.__get_feature_class_index_by_name(feature_class_name) + feature_class = self.__get_feature_class(feature_class_index) + feature_class_jump_dict = {} + for feature_index, feature in enumerate(feature_class.features): + if feature_index in feature_list: + feature_jump_list = self.get_feature_jump_list(image_range, feature_class_index, + feature_index=feature_index, + threshold=threshold, count_nan_jumps=count_nan_jumps) + feature_class_jump_dict[feature_index] = feature_jump_list + return feature_class_jump_dict + + def get_feature_class_duration_dict(self, image_range, feature_class_name, feature_list): + feature_class_index = self.__get_feature_class_index_by_name(feature_class_name) + feature_class = self.__get_feature_class(feature_class_index) + feature_class_duration_dict = {} + for feature_index, feature in enumerate(feature_class.features): + if feature_index in feature_list: + duration = self.get_feature_in_image_duration(image_range, feature_class_index, feature_index) + feature_class_duration_dict[feature_index] = duration + return feature_class_duration_dict + + def get_feature_class_movement_vector(self, image_range, feature_class_name, feature_list): + feature_class_index = self.__get_feature_class_index_by_name(feature_class_name) + feature_class = self.__get_feature_class(feature_class_index) + feature_class_movement_vectors = [] + for feature_index, feature in enumerate(feature_class.features): + if feature_list is None or feature_index in feature_list: + feature_movement_vector = self.get_feature_movement_vector(image_range, feature_class_index, + feature_index=feature_index) + feature_class_movement_vectors.append(feature_movement_vector) + return feature_class_movement_vectors + + def get_feature_class_drift_vector(self, feature_class_index): + feature_class_movement_list = self.get_feature_class_movement_list(feature_class_index) + if feature_class_movement_list == []: + return None + number_of_movements = len(feature_class_movement_list[0]) + drift_vectors = list() + for movement_number in range(number_of_movements): + if drift_vectors == []: + average_drift_vector = [0, 0] + else: + average_drift_vector = drift_vectors[-1].copy() + for feature_index in range(len(feature_class_movement_list)): + average_drift_vector[0] += (feature_class_movement_list[feature_index][movement_number][0]) / len( + feature_class_movement_list) + average_drift_vector[1] += (feature_class_movement_list[feature_index][movement_number][1]) / len( + feature_class_movement_list) + + drift_vectors.append(average_drift_vector.copy()) + return drift_vectors + + @property + def time_stamps(self): + return self.feature_organizer.scan.time_stamps diff --git a/model/data/feature/feature_class.py b/model/data/feature/feature_class.py new file mode 100644 index 0000000..5521e0a --- /dev/null +++ b/model/data/feature/feature_class.py @@ -0,0 +1,48 @@ +import logging +import typing + +from model.data.feature import feature_representation +from model.data.feature.feature_class_concretization import atom_concretization + +log = logging.getLogger(__name__) + + +class FeatureClass: + def __init__(self, feature_class_name, number_of_images, color, visualization=None): + self.number_of_images = number_of_images + self.feature_class_name = feature_class_name + self.features: typing.List[feature_representation.FeatureRepresentation] = list() + self.color = color + self._visualization = visualization + self.feature_concretization = atom_concretization.get_atom_by_name(feature_class_name) + if self.feature_concretization is not None: + log.info("The feature class name corresponded to an element in the periodic table. \n" + "Atomic number: {} Element: {} ({})".format(self.feature_concretization[0], + self.feature_concretization[2], + self.feature_concretization[1])) + + def add_feature(self): + feature = feature_representation.FeatureRepresentation(number_of_images=self.number_of_images) + self.features.append(feature) + + def remove_feature(self, feature): + self.features.remove(feature) + + def remove_all_feature_points_from_image(self, image_number): + for feature in self.features: + feature.remove_all_points(image_number) + + @property + def visualization(self): + try: + return self._visualization + except AttributeError: + self._visualization = None + return self._visualization + + @visualization.setter + def visualization(self, visualization): + self._visualization = visualization + + def __str__(self): + return str(self.feature_class_name) diff --git a/model/data/feature/feature_class_concretization/__init__.py b/model/data/feature/feature_class_concretization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/feature/feature_class_concretization/atom_concretization.py b/model/data/feature/feature_class_concretization/atom_concretization.py new file mode 100644 index 0000000..d207313 --- /dev/null +++ b/model/data/feature/feature_class_concretization/atom_concretization.py @@ -0,0 +1,134 @@ +import yaml + +periodic_tabel = [ + (1, "H", "Hydrogen"), + (2, "He", "Helium"), + (3, "Li", "Lithium"), + (4, "Be", "Beryllium"), + (5, "B", "Boron"), + (6, "C", "Carbon"), + (7, "N", "Nitrogen"), + (8, "O", "Oxygen"), + (9, "F", "Fluorine"), + (10, "Ne", "Neon"), + (11, "Na", "Sodium"), + (12, "Mg", "Magnesium"), + (13, "Al", "Aluminum"), + (14, "Si", "Silicon"), + (15, "P", "Phosphorus"), + (16, "S", "Sulfur"), + (17, "Cl", "Chlorine"), + (18, "Ar", "Argon"), + (19, "K", "Potassium"), + (20, "Ca", "Calcium"), + (21, "Sc", "Scandium"), + (22, "Ti", "Titanium"), + (23, "V", "Vanadium"), + (24, "Cr", "Chromium"), + (25, "Mn", "Manganese"), + (26, "Fe", "Iron"), + (27, "Co", "Cobalt"), + (28, "Ni", "Nickel"), + (29, "Cu", "Copper"), + (30, "Zn", "Zinc"), + (31, "Ga", "Gallium"), + (32, "Ge", "Germanium"), + (33, "As", "Arsenic"), + (34, "Se", "Selenium"), + (35, "Br", "Bromine"), + (36, "Kr", "Krypton"), + (37, "Rb", "Rubidium"), + (38, "Sr", "Strontium"), + (39, "Y", "Yttrium"), + (40, "Zr", "Zirconium"), + (41, "Nb", "Niobium"), + (42, "Mo", "Molybdenum"), + (43, "Tc", "Technetium"), + (44, "Ru", "Ruthenium"), + (45, "Rh", "Rhodium"), + (46, "Pd", "Palladium"), + (47, "Ag", "Silver"), + (48, "Cd", "Cadmium"), + (49, "In", "Indium"), + (50, "Sn", "Tin"), + (51, "Sb", "Antimony"), + (52, "Te", "Tellurium"), + (53, "I", "Iodine"), + (54, "Xe", "Xenon"), + (55, "Cs", "Cesium"), + (56, "Ba", "Barium"), + (57, "La", "Lanthanum"), + (58, "Ce", "Cerium"), + (59, "Pr", "Praseodymium"), + (60, "Nd", "Neodymium"), + (61, "Pm", "Promethium"), + (62, "Sm", "Samarium"), + (63, "Eu", "Europium"), + (64, "Gd", "Gadolinium"), + (65, "Tb", "Terbium"), + (66, "Dy", "Dysprosium"), + (67, "Ho", "Holmium"), + (68, "Er", "Erbium"), + (69, "Tm", "Thulium"), + (70, "Yb", "Ytterbium"), + (71, "Lu", "Lutetium"), + (72, "Hf", "Hafnium"), + (73, "Ta", "Tantalum"), + (74, "W", "Tungsten"), + (75, "Re", "Rhenium"), + (76, "Os", "Osmium"), + (77, "Ir", "Iridium"), + (78, "Pt", "Platinum"), + (79, "Au", "Gold"), + (80, "Hg", "Mercury"), + (81, "Tl", "Thallium"), + (82, "Pb", "Lead"), + (83, "Bi", "Bismuth"), + (84, "Po", "Polonium"), + (85, "At", "Astatine"), + (86, "Rn", "Radon"), + (87, "Fr", "Francium"), + (88, "Ra", "Radium"), + (89, "Ac", "Actinium"), + (90, "Th", "Thorium"), + (91, "Pa", "Protactinium"), + (92, "U", "Uranium"), + (93, "Np", "Neptunium"), + (94, "Pu", "Plutonium"), + (95, "Am", "Americium"), + (96, "Cm", "Curium"), + (97, "Bk", "Berkelium"), + (98, "Cf", "Californium"), + (99, "Es", "Einsteinium"), + (100, "Fm", "Fermium"), + (101, "Md", "Mendelevium"), + (102, "No", "Nobelium"), + (103, "Lr", "Lawrencium"), + (104, "Rf", "Rutherfordium"), + (105, "Db", "Dubnium"), + (106, "Sg", "Seaborgium"), + (107, "Bh", "Bohrium"), + (108, "Hs", "Hassium"), + (109, "Mt", "Meitnerium"), + (110, "Ds", "Darmstadtium"), + (111, "Rg", "Roentgenium"), + (112, "Cn", "Copernicium"), + (113, "Nh", "Nihonium"), + (114, "Fl", "Flerovium"), + (115, "Mc", "Moscovium"), + (116, "Lv", "Livermorium"), + (117, "Ts", "Tennessine"), + (118, "Og", "Oganesson") +] + + +def get_atom_by_name(name: str): + for atom_class in periodic_tabel: + if atom_class[1] == name or atom_class[2] == name: + return atom_class + + +def get_atom_by_number(atom_number: int): + for atom_class in periodic_tabel: + if atom_class[0] == atom_number: + return atom_class diff --git a/model/data/feature/feature_class_concretization/feature_color.py b/model/data/feature/feature_class_concretization/feature_color.py new file mode 100644 index 0000000..15d4a3b --- /dev/null +++ b/model/data/feature/feature_class_concretization/feature_color.py @@ -0,0 +1,22 @@ +import yaml + +try: + feature_color_dict = yaml.safe_load(open("config/feature_color.yaml")) +except FileNotFoundError: + feature_color_dict = {"14": [152, 206, 174], + "8": [223, 107, 101], + "44": [129, 129, 128], + "32": [89, 104, 141]} + + +def get_feature_class_color_by_name(feature_class_name): + try: + color = feature_color_dict[feature_class_name] + except KeyError: + color = None + return color + + +def set_feature_class_color_by_name(feature_class_name, color): + feature_color_dict[feature_class_name] = color + yaml.safe_dump(feature_color_dict, open("config/feature_color.yaml", "w")) diff --git a/model/data/feature/feature_organizer.py b/model/data/feature/feature_organizer.py new file mode 100644 index 0000000..e216154 --- /dev/null +++ b/model/data/feature/feature_organizer.py @@ -0,0 +1,525 @@ +import copy +import logging +import pickle +import typing + +import networkx +import numpy as np +import pandas as pd +import yaml +from skimage.future import graph + +from model import config +from model.data.feature import feature_analyser, feature_class +from model.saasmi_utils import saasmi + +log = logging.getLogger(__name__) + + +class FeatureOrganizer: + def __init__(self, scan, number_of_images): + self.scan = scan + self.number_of_images = number_of_images + self.feature_classes: typing.List[feature_class.FeatureClass] = list() + self.feature_analyser = feature_analyser.FeatureAnalyser(self) + self._rag_dict = {} + self._rag_generated = False + + @property + def rag_dict(self): + try: + return self._rag_dict + except AttributeError: + self._rag_dict = {} + return self._rag_dict + + @property + def rag_generated(self): + try: + return self._rag_generated + except AttributeError: + self._rag_generated = False + return self._rag_generated + + @rag_generated.setter + def rag_generated(self, rag_generated): + self._rag_generated = rag_generated + + def add_feature_class(self, feature_class_name, color=None): + feature_class_instance = feature_class.FeatureClass(feature_class_name=feature_class_name, + number_of_images=self.number_of_images, color=color) + self.feature_classes.append(feature_class_instance) + return feature_class_instance + + def add_feature(self, feature_class_index): + self.feature_classes[feature_class_index].add_feature() + + def auto_add_feature_points(self, feature_class_index, image_number, point): + if len(self.feature_classes) == 0: + self.add_feature_class("Feature_Class_Name", color=[255, 255, 0, 255]) + distance, class_index_nearest, feature_index, point_index, point_position = self.get_nearest_point(point, + image_number) + distance_assume_same_feature = self.scan.step_size[0] * config.get_value("feature_settings", + "auto_detect_feature_distance") + if distance is not None and distance < self.scan.step_size[0]: + return + if image_number > 0: + image_number_to_check = image_number - 1 + + distance, class_index_nearest, feature_index, point_index, point_position = self.get_nearest_point(point, + image_number_to_check) + log.debug("Nearest point distance {}".format(distance)) + if distance is not None and distance < distance_assume_same_feature and feature_class_index == class_index_nearest: + self.add_point_to_feature(feature_class_index=feature_class_index, feature_index=feature_index, + point=point, image_number=image_number) + log.debug("Point added to Feature Class {} Feature Index {} Image {}".format(feature_class_index, + feature_index, + image_number)) + return + self.add_feature(feature_class_index) + self.feature_classes[feature_class_index].features[-1].add_point(point, image_number) + log.debug("Point added to new feature Feature Class {} Image {}".format(feature_class_index, + image_number)) + + def add_point_to_feature(self, feature_class_index, feature_index, point, image_number, maximum_number_of_points=0): + return self.feature_classes[feature_class_index].features[feature_index].add_point(point, + image_number=image_number, + maximum_number_of_points=maximum_number_of_points) + + def remove_feature_class(self, feature_class_index=None, feature_class_name=None): + if feature_class_index is not None and self.feature_classes != []: + self.feature_classes.pop(feature_class_index) + elif feature_class_name is not None: + for feature_class in self.feature_classes: + if str(feature_class) == feature_class_name: + self.feature_classes.remove(feature_class) + + def remove_nearest_point(self, ref_position, image_number): + distance, feature_class_index, feature_index, point_index, point_position = self.get_nearest_point(ref_position, + image_number) + try: + tmp_feature = self.feature_classes[feature_class_index].features[feature_index] + except (IndexError, TypeError): + tmp_feature = None + if tmp_feature is not None: + tmp_feature.remove_point(point_position, image_number) + return feature_class_index, feature_index + + def get_nearest_point(self, ref_position, image_number): + min_distance, tmp_position = None, None + tmp_feature_class_index, tmp_feature_index, tmp_point_index = None, None, None + for feature_class_index, feature_class in enumerate(self.feature_classes): + for feature_index, feature in enumerate(feature_class.features): + nearest_distance, position, point_index = feature.get_distance_to_nearest_point(ref_position, + image_number=image_number) + if nearest_distance is not None and (min_distance is None or nearest_distance < min_distance): + min_distance = nearest_distance + tmp_position = position + tmp_feature_index = feature_index + tmp_feature_class_index = feature_class_index + tmp_point_index = point_index + return min_distance, tmp_feature_class_index, tmp_feature_index, tmp_point_index, tmp_position + + def get_features(self, feature_class_index): + return self.feature_classes[feature_class_index].features + + def get_feature_points_by_image_number(self, feature_class_index, feature_index, image_number): + try: + positions = self.feature_classes[feature_class_index].features[feature_index].feature_point_positions[ + image_number] + if positions is None: + return [] + except KeyError: + positions = [] + return positions + + def get_feature_points(self, feature_class_index, feature_index): + if self.feature_classes[feature_class_index].features[feature_index].feature_point_positions is None: + return [] + else: + return self.feature_classes[feature_class_index].features[feature_index].feature_point_positions + + def remove_feature_from_feature_class(self, feature_class_index, feature_index): + self.feature_classes[feature_class_index].features.pop(feature_index) + + def remove_feature_point(self, feature_class_index, feature_index, point_index, image_number): + self.feature_classes[feature_class_index].features[feature_index].feature_point_positions[image_number].pop( + point_index) + + def _get_latest_image_feature_points(self, feature, image_number): + tmp_image_feature_points = None + for image_index in reversed(range(image_number + 1)): + try: + tmp_image_feature_points = feature.feature_point_positions[image_index] + if tmp_image_feature_points is not None: + break + except KeyError: + pass + if tmp_image_feature_points is None: + tmp_image_feature_points = [] + return tmp_image_feature_points + + def _set_feature_point_for_previous_empty_features(self, tmp_image_feature_points, feature, image_number): + for image_index in reversed(range(image_number + 1)): + try: + if feature.feature_point_positions[image_index] == {}: + feature.feature_point_positions[image_index] = copy.deepcopy(tmp_image_feature_points) + else: + break + except KeyError: + feature.feature_point_positions[image_index] = copy.deepcopy(tmp_image_feature_points) + + def auto_fill_images(self, image_number): + for feature_class_index, feature_class in enumerate(self.feature_classes): + for feature_index, feature in enumerate(feature_class.features): + tmp_image_feature_points = self._get_latest_image_feature_points(feature, image_number) + self._set_feature_point_for_previous_empty_features(tmp_image_feature_points, feature, image_number) + + def get_feature_dict(self, image_number): + feature_dict = dict() + for feature_class_index, feature_class in enumerate(self.feature_classes): + feature_dict[str(feature_class)] = dict() + for feature_index, feature in enumerate(feature_class.features): + try: + point_list = feature.feature_point_positions[image_number] + if point_list is None: + continue + except KeyError: + continue + feature_dict[str(feature_class)][feature_index] = list() + for point in point_list: + feature_dict[str(feature_class)][feature_index].append(point) + return feature_dict + + def get_feature_class_by_string(self, feature_class_string) -> (int, feature_class.FeatureClass): + for index, feature_class in enumerate(self.feature_classes): + if str(feature_class) == feature_class_string: + return index, feature_class + return None, None + + def scale_features(self, scale_ratio): + for feature_class in self.feature_classes: + for feature_index in feature_class.features: + for image_number, point_list in feature_index.feature_point_positions.items(): + for point_index, point in enumerate(point_list): + new_point = list(point) + new_point[0] *= scale_ratio[0] + new_point[1] *= scale_ratio[1] + feature_index.feature_point_positions[image_number][point_index] = new_point + + def set_feature_from_feature_dict(self, file_dict): + feature_dict = file_dict['features'] + feature_class_colors = file_dict['feature_class_colors'] + self.feature_classes = [] + for image_number, feature_classes_dict in feature_dict.items(): + if image_number >= self.number_of_images: + continue + for feature_class_name in feature_classes_dict: + _, feature_class_instance = self.get_feature_class_by_string(feature_class_name) + if feature_class_instance is None: + feature_class_instance = self.add_feature_class(feature_class_name, + color=feature_class_colors[feature_class_name]) + for feature_index, features in feature_classes_dict[feature_class_name].items(): + for feature_points in features: + current_max_index = len(feature_class_instance.features) - 1 + for _ in range(feature_index - current_max_index): + feature_class_instance.add_feature() + feature_class_instance.features[feature_index].add_point(feature_points, image_number) + + def save_in_file(self, file_path, human_readable=False): + output_dict = dict() + features_dict = dict() + output_dict['scan_file_path'] = self.scan.file_path + output_dict['image_class'] = self.scan.current_image_class + output_dict['number_of_images'] = self.number_of_images + output_dict['features'] = features_dict + output_dict['feature_class_colors'] = self.get_feature_class_colors() + output_dict['number_of_features'] = None + output_dict['feature_class_name_list'] = list() + for image_number in range(self.number_of_images): + image_feature_dict = self.get_feature_dict(image_number=image_number) + is_empty = True + for feature_class_name, feature_dict in image_feature_dict.items(): + if output_dict['number_of_features'] is None: + output_dict['number_of_features'] = len(features_dict) + if feature_class_name not in output_dict['feature_class_name_list']: + output_dict['feature_class_name_list'].append(feature_class_name) + if feature_dict != {}: + is_empty = False + if is_empty is False: + features_dict[image_number] = self.get_feature_dict(image_number=image_number) + + self.file_saver(file_path=file_path, data=output_dict, human_readable=human_readable) + + def validate_file_to_load(self, file_path): + input_dict = self.file_loader(file_path) + reason_not_to_load = list() + try: + if input_dict['scan_file_path'].split("/")[-1] != self.scan.file_path.split("/")[-1]: + reason_not_to_load.append("Current scan file is different from the one in the feature list") + if input_dict['image_class'] != self.scan.current_image_class: + reason_not_to_load.append( + "Image classes are not the same for the loaded file and the current scan file") + if input_dict['number_of_images'] != self.number_of_images: + reason_not_to_load.append( + "The number of images in the file differs from the scan file and the feature file") + except Exception as e: + log.exception(e) + reason_not_to_load.append("Something is wrong with the file, see logs for more info") + return reason_not_to_load + + def load_features_from_file(self, file_path, feature_ratio): + input_dict = self.file_loader(file_path) + self.set_feature_from_feature_dict(input_dict) + self.scale_features(feature_ratio) + + def set_color_by_feature_class_name(self, feature_class_name, color): + index, feature_class = self.get_feature_class_by_string(feature_class_name) + feature_class.color = color + + def get_color_by_feature_class_name(self, feature_class_name): + index, feature_class = self.get_feature_class_by_string(feature_class_name) + if feature_class is not None: + return feature_class.color + + def get_color_by_feature_class_index(self, feature_class_index): + return self.feature_classes[feature_class_index].color + + def remove_all_points_from_image(self, feature_class_index, image_number): + self.feature_classes[feature_class_index].remove_all_feature_points_from_image(image_number) + return True + + def get_feature_class_colors(self): + feature_class_color_dict = {} + for feature_class in self.feature_classes: + feature_class_color_dict[str(feature_class)] = feature_class.color + return feature_class_color_dict + + def file_loader(self, file_path): + if file_path[-4:] == ".pkl": + with open(file_path, 'rb') as file: + input_dict = pickle.load(file) + else: + with open(file_path, 'rb') as file: + input_dict = yaml.safe_load(file) + return input_dict + + def file_saver(self, file_path, data, human_readable=False): + if human_readable: + with open(file_path + ".txt", 'wb') as file: + yaml.safe_dump(data, file, default_flow_style=False, sort_keys=False) + else: + with open(file_path + ".pkl", 'wb') as file: + pickle.dump(data, file, pickle.HIGHEST_PROTOCOL) + + def get_markers(self, image_number): + row, col = list(), list() + for feature_class_instance in self.feature_classes: + for feature in feature_class_instance.features: + try: + points = feature.feature_point_positions[image_number] + except KeyError: + points = [] + for point in points: + col.append(point[0]) + row.append(point[1]) + if len(row) == 0: + return None + return (row, col) + + def auto_fill_images_with_selected_features(self, current_image, feature_class_name, feature_list): + for feature_class_index, feature_class in enumerate(self.feature_classes): + if feature_class.feature_class_name != feature_class_name: + continue + for feature_index, feature in enumerate(feature_class.features): + if feature_index not in feature_list: + continue + if current_image == 0: + continue + try: + tmp_image_feature_points = feature.feature_point_positions[current_image - 1] + except KeyError: + continue + feature.feature_point_positions[current_image] = copy.deepcopy(tmp_image_feature_points) + + def rescale_features(self, x_scale, y_scale): + for feature_class_instance in self.feature_classes: + for feature in feature_class_instance.features: + for image_number in feature.feature_point_positions: + feature_points = feature.feature_point_positions[image_number] + for point_id, feature_point in enumerate(feature_points): + x, y, *z = feature_point + x *= x_scale + y *= y_scale + feature_points[point_id] = [x, y, *z] + + def get_features_exceed_maximum_number_of_points(self, maximum_number_of_points): + feature_exceed_maximum_number_of_points_list = [] + for feature_class_instance in self.feature_classes: + for feature_index, feature in enumerate(feature_class_instance.features): + for image_number in feature.feature_point_positions: + feature_points = feature.feature_point_positions[image_number] + if len(feature_points) > maximum_number_of_points: + feature_exceed_maximum_number_of_points_list.append( + [feature_class_instance.feature_class_name, feature_index, image_number]) + return feature_exceed_maximum_number_of_points_list + + def calc_centroid_by_point_list(self, point_list): + if len(point_list) == 1: + return point_list[0][:2] + elif len(point_list) > 1: + x = [p[0] for p in point_list] + y = [p[1] for p in point_list] + centroid = (sum(x) / len(point_list), sum(y) / len(point_list)) + return centroid + else: + return None + + def get_feature_rag(self, image, image_number): + feature_point_dict = {} + for feature_class_instance in self.feature_classes: + for feature_index, feature in enumerate(feature_class_instance.features): + try: + feature_points = feature.feature_point_positions[image_number] + feature_centroid = self.calc_centroid_by_point_list(feature_points) + except KeyError: + feature_centroid = None + if feature_centroid is not None: + feature_point_dict[tuple(feature_centroid)] = [feature_class_instance.feature_class_name, + feature_index] + points = list(feature_point_dict.keys()) + if len(points) == 0: + return None + row = [point[1] for point in points] + col = [point[0] for point in points] + markers_row_col = (row, col) + repaired_image = saasmi.repair_nan_inside_of_image(image) + nan_mask = np.isnan(repaired_image) + image = np.nan_to_num(repaired_image) + markers, marker_list = saasmi.marking(image, markers=markers_row_col) + + labels = saasmi.segment(image, markers, marker_list=marker_list, nan_mask=nan_mask, + beta=0) + + # Has to be casted to graph since rag does not support relabeling + rag = networkx.Graph(graph.RAG(labels)) + if rag.has_node(0): + rag.remove_node(0) + self.__add_feature_information_to_rag(rag, markers_row_col, labels, feature_point_dict) + return rag + + def __add_feature_information_to_rag(self, rag, markers_row_col, labeled_image, feature_point_dict): + marker_array = np.asarray(markers_row_col).T + relabel_dict = {} + for label in np.unique(labeled_image): + if label == 0: + continue + y, x = marker_array[label - 1] + rag.nodes[label]['marker'] = (x, y) + rag.nodes[label]['feature_class_name'] = feature_point_dict[(x, y)][0] + rag.nodes[label]['feature_index'] = feature_point_dict[(x, y)][1] + relabel_dict[label] = "{} {}".format(feature_point_dict[(x, y)][0], feature_point_dict[(x, y)][1]) + networkx.relabel_nodes(rag, relabel_dict, copy=False) + + def get_rag_jump_information(self, selected_feature_classes, selected_feature_dict): + rag_information_dict = {} + for image_number, rag in self.rag_dict.items(): + try: + rag_next_image = self.rag_dict[image_number + 1] + except KeyError: + break + if rag.edges != rag_next_image.edges: + neighbor_change_list = self.__get_rag_neighbor_change_list(rag, rag_next_image, + selected_feature_classes, + selected_feature_dict) + if neighbor_change_list != []: + rag_information_dict[image_number] = neighbor_change_list + return rag_information_dict + + def __get_rag_neighbor_change_list(self, rag, rag_2, selected_feature_classes, selected_feature_dict): + neighbor_change_list = [] + for node in rag.nodes: + node_feature_class_name, node_feature_index = self.__get_feature_class_name_and_index_by_node(node) + if node_feature_class_name in selected_feature_classes and node_feature_index in selected_feature_dict[ + node_feature_class_name]: + if node in rag_2 and set(rag.neighbors(node)) != set(rag_2.neighbors(node)): + node_change_information = self.__get_node_change_information(node, rag, rag_2) + neighbor_change_list.append([node, node_change_information]) + return neighbor_change_list + + def get_rag_neigbhbor_dict(self, rag, selected_feature_classes, selected_feature_dict): + rag_neighbor_dict = {} + for node in rag.nodes: + node_feature_class_name, node_feature_index = self.__get_feature_class_name_and_index_by_node(node) + if node_feature_class_name in selected_feature_classes and node_feature_class_name not in rag_neighbor_dict: + rag_neighbor_dict[node_feature_class_name] = {} + if node_feature_class_name in selected_feature_classes and node_feature_index in selected_feature_dict[ + node_feature_class_name]: + node_rag_change_information_dict = {} + for neighbor_node in rag.neighbors(node): + feature_class_name, feature_index = self.__get_feature_class_name_and_index_by_node(neighbor_node) + if feature_class_name not in node_rag_change_information_dict: + node_rag_change_information_dict[feature_class_name] = 1 + else: + node_rag_change_information_dict[feature_class_name] += 1 + rag_neighbor_dict[node_feature_class_name][node_feature_index] = node_rag_change_information_dict + return rag_neighbor_dict + + def __get_feature_class_name_and_index_by_node(self, node): + node_feature_class_name, node_feature_index_str = str(node).rsplit(" ", 1) + node_feature_index = int(node_feature_index_str) + return node_feature_class_name, node_feature_index + + def __get_node_change_information(self, node, rag, rag_2): + node_rag_change_information_dict = {} + for neighbor_node in rag.neighbors(node): + feature_class_name, feature_index = self.__get_feature_class_name_and_index_by_node(neighbor_node) + if feature_class_name not in node_rag_change_information_dict: + node_rag_change_information_dict[feature_class_name] = 1 + else: + node_rag_change_information_dict[feature_class_name] += 1 + + node_rag_2_change_information_dict = {} + for neighbor_node in rag_2.neighbors(node): + feature_class_name, feature_index = self.__get_feature_class_name_and_index_by_node(neighbor_node) + if feature_class_name not in node_rag_2_change_information_dict: + node_rag_2_change_information_dict[feature_class_name] = 1 + else: + node_rag_2_change_information_dict[feature_class_name] += 1 + return [node_rag_change_information_dict, node_rag_2_change_information_dict] + + @staticmethod + def _get_nearest_point_to_point_list(point, point_array): + deltas = point_array - point[:2] + dist_2 = np.einsum('ij,ij->i', deltas, deltas) + position = point_array[np.argmin(dist_2)].tolist() + position = [*position, *point[2:]] + return position + + def apply_feature_grid_to_feature_points(self, feature_grid_points): + feautre_grid_point_array = np.array(feature_grid_points) + for feature_class in self.feature_classes: + for feature_index in feature_class.features: + for image_number, point_list in feature_index.feature_point_positions.items(): + for point_index, point in enumerate(point_list): + new_position = self._get_nearest_point_to_point_list(point, + feautre_grid_point_array) + + point_list[point_index] = new_position + + def get_feature_lines(self): + line_pandas = pd.DataFrame( + columns=['image_number', 'feature_class', 'feature_index', 'start_point', 'end_point', 'length']) + for feature_class in self.feature_classes: + for feature_index, feature in enumerate(feature_class.features): + for image_number, point_list in feature.feature_point_positions.items(): + if len(point_list) == 2: + length = np.sqrt( + (point_list[1][0] - point_list[0][0]) ** 2 + (point_list[1][1] - point_list[0][1]) ** 2) + line_pandas = line_pandas.append( + {"image_number": image_number, "feature_class": feature_class.feature_class_name, + "feature_index": feature_index, + "start_point": point_list[0], 'end_point': point_list[1], + 'length': length}, + ignore_index=True) + return line_pandas diff --git a/model/data/feature/feature_representation.py b/model/data/feature/feature_representation.py new file mode 100644 index 0000000..b1d39a0 --- /dev/null +++ b/model/data/feature/feature_representation.py @@ -0,0 +1,47 @@ +import math + + +class FeatureRepresentation: + __slots__ = 'number_of_images', 'feature_point_positions' + + def __init__(self, number_of_images): + self.number_of_images = number_of_images + self.feature_point_positions = {} + + def add_point(self, position, image_number, maximum_number_of_points=0): + try: + if len(self.feature_point_positions[image_number]) >= maximum_number_of_points > 0: + return False + else: + self.feature_point_positions[image_number].append(position) + return True + except (KeyError, AttributeError): + self.feature_point_positions[image_number] = list() + self.feature_point_positions[image_number].append(position) + return True + + def remove_point(self, position, image_number): + try: + self.feature_point_positions[image_number].remove(position) + except KeyError: + pass + + def get_distance_to_nearest_point(self, position, image_number): + min_distance, current_position, point_index = None, None, None + try: + feature_point_list = self.feature_point_positions[image_number] + except KeyError: + return None, None, None + if feature_point_list is None: + return None, None, None + for i, feature_point in enumerate(feature_point_list): + cur_distance = math.hypot(feature_point[0] - position[0], + feature_point[1] - position[1]) + if min_distance is None or cur_distance < min_distance: + min_distance = cur_distance + current_position = feature_point + point_index = i + return min_distance, current_position, point_index + + def remove_all_points(self, image_number): + self.feature_point_positions[image_number] = list() diff --git a/model/data/readers/__init__.py b/model/data/readers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/readers/arn_reader.py b/model/data/readers/arn_reader.py new file mode 100644 index 0000000..f55e8c2 --- /dev/null +++ b/model/data/readers/arn_reader.py @@ -0,0 +1,77 @@ +import logging + +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + +import re + +import numpy as np + + +def get_record_bytes(file_content): + file_content_str = file_content.decode(errors="ignore") + record_bytes_regex = r"RECORD_BYTES.*= ([0-9]+)" + matches = re.findall(record_bytes_regex, file_content_str) + try: + return int(matches[0]) + except Exception as e: + print("Header has no Record Bytes") + raise e + + +def get_image_start_pointer(file_content): + file_content_str = file_content.decode(errors="ignore") + record_bytes_regex = r"IMAGE.*= ([0-9]+)" + matches = re.findall(record_bytes_regex, file_content_str) + try: + return int(matches[0]) + except Exception as e: + print("Header has no Image Start Pointer") + raise e + + +def get_number_of_lines(file_content): + file_content_str = file_content.decode(errors="ignore") + record_bytes_regex = r"LINES.*= ([0-9]+)" + matches = re.findall(record_bytes_regex, file_content_str) + try: + return int(matches[0]) + except Exception as e: + print("Header has no Lines") + raise e + + +def parse_header(file_content): + file_content_str = file_content.decode(errors="ignore") + record_bytes_regex = r"(.*)= ([0-9]+)" + matches = re.findall(record_bytes_regex, file_content_str) + header_parsed = {key.strip(): value for key, value in matches} + return header_parsed + + +def arn_reader(file_content): + record_bytes = get_record_bytes(file_content) + image_start = get_image_start_pointer(file_content) + lines = get_number_of_lines(file_content) + header = parse_header(file_content) + image = file_content[record_bytes * (image_start - 1):] + image = np.frombuffer(image, dtype="int16") + image = image.reshape(lines, -1) + return header, image + + +def read_file_to_scan(file_path) -> scan_file_representation.Scan: + scan = scan_file_representation.Scan(file_path) + with open(file_path, "rb") as file: + file_content = file.read() + header, image = arn_reader(file_content) + image = image.astype(float) + scan.scan_information = {**scan.scan_information, **header} + image_list = [image] + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + image_collection_dict = {"None": scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan diff --git a/model/data/readers/avi_reader.py b/model/data/readers/avi_reader.py new file mode 100644 index 0000000..7984b3e --- /dev/null +++ b/model/data/readers/avi_reader.py @@ -0,0 +1,32 @@ +import logging + +import cv2 + +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + + +def read_file_to_scan(file_path) -> scan_file_representation.Scan: + scan = scan_file_representation.Scan(file_path) + video_capture = cv2.VideoCapture(file_path) + fps = video_capture.get(cv2.CAP_PROP_FPS) + image_list = list() + while (video_capture.isOpened()) and len(image_list) < 1000: + ret, frame = video_capture.read() + if ret == True: + image_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + image_list.append(image_gray.astype('float64')) + else: + video_capture.release() + time_stamps = [] + for i in range(len(image_list)): + time_stamps.append(i / fps) + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + + image_collection_dict = { + "None": scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, time_stamps=time_stamps, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan diff --git a/model/data/readers/gwy_reader.py b/model/data/readers/gwy_reader.py new file mode 100644 index 0000000..c7f9761 --- /dev/null +++ b/model/data/readers/gwy_reader.py @@ -0,0 +1,44 @@ +import logging + +import gwyfile + +from model import config +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + + +def get_ui_parameters(file_path): + obj = gwyfile.load(file_path) + channels = gwyfile.util.get_datafields(obj) + return_channels = list() + for channel in channels: + return_channels.append(str(channel)) + return return_channels + + +def load_file(file_path, gwy_channel=None): + obj = gwyfile.load(file_path) + channels = gwyfile.util.get_datafields(obj) + if gwy_channel is not None: + channel = channels[gwy_channel] + else: + channel = next(iter(channels.values())) + image = channel.data + meta_data = dict(channel) + del meta_data["data"] + return meta_data, image + + +def read_file_to_scan(file_path) -> scan_file_representation.Scan: + scan = scan_file_representation.Scan(file_path) + gwy_channel = config.get_value("import_gwy", "channel") + meta_data, image = load_file(file_path, gwy_channel=gwy_channel) + image_list = [image] + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + scan_image_information = {**scan_image_information, **meta_data} + image_collection_dict = {"None": scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan diff --git a/model/data/readers/h5_reader.py b/model/data/readers/h5_reader.py new file mode 100644 index 0000000..eacf8a5 --- /dev/null +++ b/model/data/readers/h5_reader.py @@ -0,0 +1,302 @@ +import copy +import logging + +import h5py +import numpy as np + +from model import config +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation +from model.data.scan_representation import scan_image_collection + +log = logging.getLogger(__name__) +ATTRIBUTES_TO_PLOT = ["counter", "uniqueId", "digitizerTimeStamp"] + + +def _save_add_to_dict_list(plot_dict, dict_key, value): + if dict_key not in plot_dict: + plot_dict[dict_key] = [] + plot_dict[dict_key].append(value) + return plot_dict + + +def add_plots_to_plot_dict(key, data, plot_dict): + for attribute in ATTRIBUTES_TO_PLOT: + if attribute != "digitizerTimeStamp": + attribute_data = data[key].attrs[attribute] + _save_add_to_dict_list(plot_dict, attribute, attribute_data) + else: + attribute_data = data[key].attrs[attribute] + for index, time_stamp in enumerate(attribute_data): + attribute_key = attribute + "_{}".format(index) + _save_add_to_dict_list(plot_dict, attribute_key, time_stamp) + + +def add_base_name_to_plot_dict(plot_dict, base_name): + try: + key_int = int(base_name) + except: + key_int = None + if key_int is not None: + _save_add_to_dict_list(plot_dict, "base_name", key_int) + + +def generate_plot_dict_by_data(data, plot_dict, remove_empty_data_sets=True): + for key in list(data.keys()): + if remove_empty_data_sets and len(data[key][()].astype('float32')) == 0: + _save_add_to_dict_list(plot_dict, "empty_data_sets", 1) + else: + _save_add_to_dict_list(plot_dict, "empty_data_sets", 0) + _save_add_to_dict_list(plot_dict, 'dataset length', len(data[key][()].astype('float32'))) + add_plots_to_plot_dict(key, data, plot_dict) + add_base_name_to_plot_dict(plot_dict, key) + + +def __read_key_from_h5_file(key, data, result_list, time_stamp_list): + try: + result_list.append([key, data[key][()].astype('float32')]) + time_stamp_list.append(data[key].attrs.get('digitizerTimeStamp')[2] * 1e-9) + except: + log.warning("key: {key} is not valid data --> skipped".format(key=key)) + + +def __calculate_frequency_sampling(time_delta_per_image, number_of_samples_per_image): + sampling_delta = time_delta_per_image / number_of_samples_per_image + frequency_sampling = 1 / sampling_delta + return frequency_sampling + + +def remove_empty_data(input_data, time_stamps): + output_data = list() + output_time_stamps = list() + for image_number in range(len(input_data)): + if input_data[image_number][1].size > 0: + output_data.append(input_data[image_number]) + output_time_stamps.append(time_stamps[image_number]) + return output_data, output_time_stamps + + +def remove_zoom(cleaned_data, cleaned_time_stamps, data_range): + cleaned_data_index = 0 + output_data = list() + output_time_stamps = list() + for data_range_index in range(len(data_range)): + range_series = data_range.iloc[data_range_index] + output_data.append(cleaned_data[cleaned_data_index]) + output_time_stamps.append(cleaned_time_stamps[cleaned_data_index]) + if range_series["x_range"] < 0.90 * data_range["x_range"].max() and range_series["y_range"] < 0.90 * data_range[ + "y_range"].max(): + log.info("Zoom was removed at image {}".format(data_range_index)) + break + cleaned_data_index += 1 + return output_data, output_time_stamps + + +def apply_ideal_xy_values_to_all_images(ideal_waveform, cleaned_data, cleaned_time_stamps): + index_step_size = len(ideal_waveform) / len(cleaned_data[0]) + index_list = np.round(np.arange(0, len(cleaned_data[0])) * index_step_size).astype("int") + ideal_xy = ideal_waveform[index_list] + for data in cleaned_data: + data[:, :2] = ideal_xy + return cleaned_data, cleaned_time_stamps + + +def apply_first_xy_values_to_all_images(cleaned_data, cleaned_time_stamps): + first_image_xy = cleaned_data[0][:, :2] + for data in cleaned_data: + data[:, :2] = first_image_xy + return cleaned_data, cleaned_time_stamps + + +def return_chosen_images_to_import(cleaned_data, cleaned_time_stamps): + start_image, end_image = config.config['import_h5']["import_images"] + if end_image <= start_image or end_image > len(cleaned_data): + log.info("End image was not chosen properly, it was set to the maximum number of images") + end_image = len(cleaned_data) + if start_image < 0 or start_image > len(cleaned_data): + log.info("Start image was not chosen properly, it was set to 0") + start_image = 0 + config.config['import_h5']["import_images"] = [start_image, end_image] + log.info("Images from {} to {} are imported".format(start_image, end_image)) + return cleaned_data[start_image:end_image], cleaned_time_stamps[start_image:end_image] + + +def read_file_to_scan(file_path, number_of_bins=100, cut_zoom=True, remove_offset=True, + use_first_image_xy_values=False, use_ideal_xy_values=False, + ) -> scan_file_representation.Scan: + key_data_list = list() + time_stamp_list = list() + plot_dict = {} + with h5py.File(file_path, 'r') as file: + try: + h5_data = file['data'] + except KeyError: + h5_data = file + for key in list(h5_data.keys()): + __read_key_from_h5_file(key, h5_data, result_list=key_data_list, time_stamp_list=time_stamp_list) + generate_plot_dict_by_data(h5_data, plot_dict) + ideal_wave_form = get_ideal_wave_form(file) + key_data_list_cleaned, time_stamps_cleaned = remove_empty_data(key_data_list, time_stamp_list) + try: + frequency_sampling = __calculate_frequency_sampling(time_stamps_cleaned[1] - time_stamps_cleaned[0], + key_data_list_cleaned[0][1].shape[0]) + except IndexError: + frequency_sampling = len(key_data_list_cleaned[0][1]) + key_data_list_cleaned, time_stamps_cleaned = return_chosen_images_to_import(key_data_list_cleaned, + time_stamps_cleaned) + key_list = [] + data_cleaned_list = [] + for key, data in key_data_list_cleaned: + key_list.append(key) + data_cleaned_list.append(data) + if remove_offset: + data_cleaned_list = readers_help_functions.remove_offset_from_data(data_cleaned_list) + data_range_df = readers_help_functions.generate_data_range_df(data_cleaned_list) + if cut_zoom: + data_cleaned_list, time_stamps_cleaned = remove_zoom(cleaned_data=data_cleaned_list, + cleaned_time_stamps=time_stamps_cleaned, + data_range=data_range_df) + if use_ideal_xy_values: + data_cleaned_list, time_stamps_cleaned = apply_ideal_xy_values_to_all_images(ideal_wave_form, + cleaned_data=data_cleaned_list, + cleaned_time_stamps=time_stamps_cleaned) + elif use_first_image_xy_values: + data_cleaned_list, time_stamps_cleaned = apply_first_xy_values_to_all_images(cleaned_data=data_cleaned_list, + cleaned_time_stamps=time_stamps_cleaned) + time_stamps_cleaned -= time_stamps_cleaned[0] + grid_2d_window = config.config['import_h5']['visualyse_window'] + grid_2d_method = config.config['import_h5']['to_grid_method'] + image_range = copy.copy(config.config['import_h5']["import_images"]) + scan_information = {"File Path": file_path, + "Image range": image_range, + } + scan = scan_file_representation.Scan(file_path=file_path, number_of_bins=number_of_bins, + grid_2d_window=grid_2d_window, grid_2d_method=grid_2d_method, + frequency_sampling=frequency_sampling, scan_information=scan_information, + plotable_dict=plot_dict) + image_collection_dict = generate_image_collection_dict(scan=scan, signal_1d_list=data_cleaned_list, + time_stamps=time_stamps_cleaned, + number_of_bins=number_of_bins, + ideal_wave_form=ideal_wave_form, image_ids=key_list) + scan.set_image_collection(image_collection_dict) + return scan + + +def generate_image_collection_dict(scan, signal_1d_list, time_stamps, number_of_bins, ideal_wave_form=None, + image_ids=None): + image_collection_dict = {} + try: + time_stamps_delta = time_stamps[1] - time_stamps[0] + except IndexError: + time_stamps_delta = 1 + scan_directions = config.get_value("import_h5", "scan_directions") + ideal_wave_forms = [ideal_wave_form] + if 'In and Out combined' in scan_directions: + scan_image_information = readers_help_functions.get_scan_image_information(signal_1d_list, + number_of_bins=number_of_bins, + scan_type="1d") + image_collection_dict['In and Out combined'] = scan_image_collection.ScanGridImageCollection(scan=scan, + signal_1d_list=signal_1d_list, + time_stamps=time_stamps, + scan_image_information=scan_image_information, + ideal_wave_forms=ideal_wave_forms, + image_id_list=image_ids) + if 'Out' in scan_directions or 'In' in scan_directions or 'In and Out seperated' in scan_directions: + inward_scan_list, outward_scan_list = __seperate_1d_signals_into_inward_and_outward_scans(signal_1d_list) + time_stamps_inward = time_stamps + (time_stamps_delta / 2) + if ideal_wave_form is not None: + inward_ideal_signal, outward_ideal_signal = __seperate_1d_signal_into_inward_and_outward_signals( + ideal_wave_form) + inward_ideal_wave_forms = [inward_ideal_signal] + outward_ideal_wave_forms = [outward_ideal_signal] + else: + inward_ideal_wave_forms = [None] + outward_ideal_wave_forms = [None] + + if 'Out' in scan_directions: + scan_image_information = readers_help_functions.get_scan_image_information(outward_scan_list, + number_of_bins=number_of_bins, + scan_type="1d") + image_collection_dict['Out'] = scan_image_collection.ScanGridImageCollection(scan=scan, + signal_1d_list=outward_scan_list, + time_stamps=time_stamps, + scan_image_information=scan_image_information, + ideal_wave_forms=outward_ideal_wave_forms, + image_id_list=image_ids) + if "In" in scan_directions: + scan_image_information = readers_help_functions.get_scan_image_information(inward_scan_list, + number_of_bins=number_of_bins, + scan_type="1d") + image_collection_dict['In'] = scan_image_collection.ScanGridImageCollection(scan=scan, + signal_1d_list=inward_scan_list, + time_stamps=time_stamps_inward, + scan_image_information=scan_image_information, + ideal_wave_forms=inward_ideal_wave_forms, + image_id_list=image_ids) + if 'In and Out seperated' in scan_directions: + seperated_scan_list = list() + seperated_time_stamp_list = list() + seperated_ideal_wave_forms = list() + for i in range(len(inward_scan_list)): + seperated_scan_list.append(outward_scan_list[i]) + seperated_scan_list.append(inward_scan_list[i]) + seperated_time_stamp_list.append(time_stamps[i]) + seperated_time_stamp_list.append(time_stamps_inward[i]) + if ideal_wave_form is not None: + seperated_ideal_wave_forms.append(outward_ideal_signal) + seperated_ideal_wave_forms.append(inward_ideal_signal) + else: + seperated_ideal_wave_forms = [None, None] + seperated_image_ids = [] + for image_id in image_ids: + seperated_image_ids.extend([image_id, image_id]) + scan_image_information = readers_help_functions.get_scan_image_information(seperated_scan_list, + number_of_bins=number_of_bins, + scan_type="1d") + image_collection_dict['In and Out seperated'] = scan_image_collection.ScanGridImageCollection(scan=scan, + signal_1d_list=seperated_scan_list, + time_stamps=seperated_time_stamp_list, + scan_image_information=scan_image_information, + ideal_wave_forms=seperated_ideal_wave_forms, + image_id_list=seperated_image_ids) + return image_collection_dict + + +def __seperate_1d_signals_into_inward_and_outward_scans(signal_1d_list): + outward_scan_list = list() + inward_scan_list = list() + for index, image in enumerate(signal_1d_list): + inward_signal, outward_signal = __seperate_1d_signal_into_inward_and_outward_signals(image) + outward_scan_list.append(outward_signal) + inward_scan_list.append(inward_signal) + return inward_scan_list, outward_scan_list + + +def __seperate_1d_signal_into_inward_and_outward_signals(signal_1d): + max_index_x = np.argmax(signal_1d[:, 0]) + max_index_y = np.argmax(signal_1d[:, 1]) + if max_index_y > max_index_x: + max_index = max_index_y + else: + max_index = max_index_x + + outward_signal = signal_1d[:max_index] + inward_signal = signal_1d[max_index:] + # outward_signal= signal_1d[:int(len(signal_1d) / 2)] + # inward_signal = signal_1d[int(len(signal_1d) / 2):] + return inward_signal, outward_signal + + +def get_ideal_wave_form(file): + try: + data = file['waveforms_ideal'] + for key in data.keys(): + ideal_waveform_signal = data[key][()].astype('float32') + break + except KeyError: + return None + + ideal_waveform_signal[:, 0] = ideal_waveform_signal[:, 0] - ideal_waveform_signal[:, 1] + ideal_waveform_signal[:, 1] = ideal_waveform_signal[:, 3] - ideal_waveform_signal[:, 2] + ideal_waveform_signal = np.column_stack((ideal_waveform_signal[:, 0], ideal_waveform_signal[:, 1])) + return ideal_waveform_signal diff --git a/model/data/readers/image_format_reader.py b/model/data/readers/image_format_reader.py new file mode 100644 index 0000000..6854037 --- /dev/null +++ b/model/data/readers/image_format_reader.py @@ -0,0 +1,41 @@ +import logging + +import imageio +import numpy as np +from PIL import Image + +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + + +def read_file_to_scan(file_path) -> scan_file_representation.Scan: + scan = scan_file_representation.Scan(file_path) + if file_path[-4:] == ".tif" or file_path[-5:] == ".tiff": + image = np.array(Image.open(file_path).convert('L')) + else: + image = np.array(imageio.imread(file_path, as_gray=True)) + image_list = [image] + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + image_collection_dict = {"None": scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan + + +def read_files_to_scan(file_paths): + file_paths.sort() + scan = scan_file_representation.Scan(str(file_paths)) + image_list = [] + for file_path in file_paths: + if file_path[-4:] == ".tif" or file_path[-5:] == ".tiff": + image = np.array(Image.open(file_path).convert('L')) + else: + image = imageio.imread(file_path, as_gray=True) + image_list.append(image) + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + image_collection_dict = {"None": scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan diff --git a/model/data/readers/npy_reader.py b/model/data/readers/npy_reader.py new file mode 100644 index 0000000..67e9de4 --- /dev/null +++ b/model/data/readers/npy_reader.py @@ -0,0 +1,65 @@ +import logging + +import numpy as np + +from model import config +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation +from model.data.scan_representation import scan_image_collection + +log = logging.getLogger(__name__) +ATTRIBUTES_TO_PLOT = ["counter", "uniqueId", "digitizerTimeStamp"] + + +def seperate_file_data(file_data): + image_list = [] + separate_indicators = np.unique(np.argwhere(np.isnan(file_data))[:, 0]) + image_start = 0 + for separate_indicator in separate_indicators: + image_list.append(file_data[image_start:int(separate_indicator)]) + image_start = int(separate_indicator) + 1 + image_list.append(file_data[image_start:]) + return image_list + + +def get_data(file_path): + file_data = np.load(file_path, allow_pickle=True) + image_list = seperate_file_data(file_data) + return image_list + + +def read_file_to_scan(file_path, frequency_sampling=48000, number_of_bins=100) -> scan_file_representation.Scan: + grid_2d_window = config.config['import_npy']['visualyse_window'] + grid_2d_method = config.config['import_npy']['to_grid_method'] + scan_information = {"File Path": file_path, + } + data_cleaned_list = get_data(file_path) + time_stamps_cleaned = list(range(len(data_cleaned_list))) + scan = scan_file_representation.Scan(file_path=file_path, number_of_bins=number_of_bins, + grid_2d_window=grid_2d_window, grid_2d_method=grid_2d_method, + frequency_sampling=frequency_sampling, scan_information=scan_information) + image_collection_dict = generate_image_collection_dict(scan=scan, signal_1d_list=data_cleaned_list, + time_stamps=time_stamps_cleaned, + number_of_bins=number_of_bins) + scan.set_image_collection(image_collection_dict) + return scan + + +def generate_image_collection_dict(scan, signal_1d_list, time_stamps, number_of_bins, ideal_wave_form=None, + image_ids=None): + image_collection_dict = {} + try: + time_stamps_delta = time_stamps[1] - time_stamps[0] + except IndexError: + time_stamps_delta = 1 + ideal_wave_forms = [ideal_wave_form] + scan_image_information = readers_help_functions.get_scan_image_information(signal_1d_list, + number_of_bins=number_of_bins, + scan_type="1d") + image_collection_dict['default'] = scan_image_collection.ScanGridImageCollection(scan=scan, + signal_1d_list=signal_1d_list, + time_stamps=time_stamps, + scan_image_information=scan_image_information, + ideal_wave_forms=ideal_wave_forms, + image_id_list=image_ids) + return image_collection_dict diff --git a/model/data/readers/readers_help_functions.py b/model/data/readers/readers_help_functions.py new file mode 100644 index 0000000..fb91b89 --- /dev/null +++ b/model/data/readers/readers_help_functions.py @@ -0,0 +1,42 @@ +import numpy as np +import pandas as pd + + +def generate_data_range_df(data_cleaned): + data_range = pd.DataFrame(columns=['x_min', "x_max", "y_min", "y_max", "z_min", "z_max"]) + for image_number in range(len(data_cleaned)): + range_dict = dict() + range_dict["x_min"] = data_cleaned[image_number][:, 0].min() + range_dict["x_max"] = data_cleaned[image_number][:, 0].max() + range_dict["x_range"] = range_dict["x_max"] - range_dict["x_min"] + range_dict["y_min"] = data_cleaned[image_number][:, 1].min() + range_dict["y_max"] = data_cleaned[image_number][:, 1].max() + range_dict["y_range"] = range_dict["y_max"] - range_dict["y_min"] + range_dict["z_min"] = data_cleaned[image_number][:, 2].min() + range_dict["z_max"] = data_cleaned[image_number][:, 2].max() + range_dict["z_range"] = range_dict["z_max"] - range_dict["z_min"] + data_range = data_range.append(range_dict, ignore_index=True) + return data_range + + +def get_scan_image_information(image_list, number_of_bins=None, scan_type=None): + scan_image_information = {} + scan_image_information["Number of Images"] = len(image_list) + if scan_type is None: + scan_image_information["Number of Measure Points per Image"] = np.prod(image_list[0].shape) + else: + scan_image_information["Number of Measure Points per Image"] = image_list[0].shape[0] + if number_of_bins is not None: + scan_image_information["Resolution"] = [number_of_bins, number_of_bins] + else: + scan_image_information["Resolution"] = image_list[0].shape + return scan_image_information + + +def remove_offset_from_data(data): + for image in data: + image[:, 0] -= image[:, 0].mean() + image[:, 1] -= image[:, 1].mean() + image[:, 2] -= image[:, 2].mean() + + return data diff --git a/model/data/readers/stp_reader.py b/model/data/readers/stp_reader.py new file mode 100644 index 0000000..4ef0d0c --- /dev/null +++ b/model/data/readers/stp_reader.py @@ -0,0 +1,187 @@ +import logging +import re +import struct + +import numpy as np + +from model.data.readers import gwy_reader, readers_help_functions +from model.data.scan_representation import scan_image_collection, scan_file_representation + +log = logging.getLogger(__name__) + + +class WsxmReader: + @staticmethod + def __get_shape(meta_data): + shape_columns = int(re.findall(r'Number of columns: (\d+)', meta_data)[0]) + shape_rows = int(re.findall(r'Number of rows: (\d+)', meta_data)[0]) + assert (shape_rows != 0 and shape_columns != 0) + shape = (shape_rows, shape_columns) + return shape + + @staticmethod + def __get_x_amplitude(header_data): + value_list = re.findall(r'X Amplitude: (\d+\.\d+)', header_data) + if len(value_list) == 0: + value_list = re.findall(r'X Amplitude: (\d+)', header_data) + x_amplitude = float(value_list[0]) + return x_amplitude + + @staticmethod + def __get_y_amplitude(header_data): + value_list = re.findall(r'Y Amplitude: (\d+\.\d+)', header_data) + if len(value_list) == 0: + value_list = re.findall(r'Y Amplitude: (\d+)', header_data) + y_amplitude = float(value_list[0]) + return y_amplitude + + @staticmethod + def _meta_data_parser(header_data): + meta_data = {} + meta_data["shape"] = WsxmReader.__get_shape(header_data) + meta_data["x_amplitude"] = WsxmReader.__get_x_amplitude(header_data) + meta_data["y_amplitude"] = WsxmReader.__get_y_amplitude(header_data) + return meta_data + + @staticmethod + def parse_file_content(file_content): + header_data, image_data = file_content.split(b"[Header end]\r\n") + header_data = header_data.decode("utf-8", errors='ignore') + meta_data = WsxmReader._meta_data_parser(header_data) + image = WsxmReader.generate_float_array(image_data, meta_data["shape"]) + return meta_data, image + + @staticmethod + def generate_float_array(numbers, shape): + float_bytes = [] + float_numbers = [] + for number in numbers: + float_bytes.append(number) + if len(float_bytes) == 8: + float_numbers.append(struct.unpack('d', bytes(float_bytes))) + float_bytes = [] + np_array = np.array(float_numbers, dtype=np.float64) + np_array_reshaped = np_array.reshape(shape) + np_array_rotated = np.fliplr(np.flipud(np_array_reshaped)) + return np_array_rotated + + +class WsxmGwyReader: + @staticmethod + def __get_shape(header_data): + x_res = struct.unpack("i", header_data.split(b"xres")[1].split(b"yres")[0][2:6]) + y_res = struct.unpack("i", header_data.split(b"yres")[1].split(b"xreal")[0][2:6]) + shape = (x_res, y_res) + return shape + + @staticmethod + def __get_x_amplitude(header_data): + return 1 + + @staticmethod + def __get_y_amplitude(header_data): + return 1 + + @staticmethod + def _meta_data_parser(header_data): + meta_data = {} + meta_data["shape"] = WsxmGwyReader.__get_shape(header_data) + meta_data["x_amplitude"] = WsxmGwyReader.__get_x_amplitude(header_data) + meta_data["y_amplitude"] = WsxmGwyReader.__get_y_amplitude(header_data) + return meta_data + + @staticmethod + def parse_file_content(file_content): + _, header_data, image_data, *_ = file_content.split(b"data") + meta_data = WsxmGwyReader._meta_data_parser(header_data) + image = WsxmGwyReader.generate_float_array(image_data, meta_data["shape"]) + return meta_data, image + + @staticmethod + def generate_float_array(image_data, shape): + float_bytes = [] + float_numbers = [] + image_data = image_data[0:-9] + for number in image_data: + float_bytes.append(number) + if len(float_bytes) == 8: + float_numbers.append(struct.unpack('float', bytes(float_bytes))) + float_bytes = [] + np_array = np.array(float_numbers, dtype=np.float64) + np_array_reshaped = np_array.reshape(shape) + np_array_rotated = np.fliplr(np.flipud(np_array_reshaped)) + return np_array_rotated + + +def read_file_to_scan(file_path): + scan = scan_file_representation.Scan(str(file_path)) + meta_data, image = load_file(file_path) + if image is None: + return None + image_list = [image] + image_collection_key = "None" + try: + step_size_y = meta_data['y_amplitude'] / meta_data["shape"][0] + step_size_x = meta_data['x_amplitude'] / meta_data["shape"][1] + step_size = [step_size_x, step_size_y] + except KeyError: + step_size = None + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + scan_image_information = {**scan_image_information, **meta_data} + image_collection_dict = { + image_collection_key: scan_image_collection.ScanGridImageCollection(scan=scan, images_2d=image_list, + step_size=step_size, + scan_image_information=scan_image_information)} + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan + + +def read_files_to_scan(file_paths): + file_paths.sort() + scan = scan_file_representation.Scan(str(file_paths)) + image_dict = {} + image_collection_dict = {} + meta_data = {} + for file_path in file_paths: + image_class_name = "None" + meta_data, image = load_file(file_path) + if image is None: + return None + if image_class_name not in image_dict: + image_dict[image_class_name] = [image] + else: + image_dict[image_class_name].append(image) + if meta_data is not None: + step_size_y = meta_data['y_amplitude'] / meta_data["shape"][0] + step_size_x = meta_data['x_amplitude'] / meta_data["shape"][1] + step_size = [step_size_x, step_size_y] + else: + step_size = None + for image_class_name, image_list in image_dict.items(): + image_collection_dict[image_class_name] = scan_image_collection.ScanGridImageCollection( + scan=scan, images_2d=image_list, step_size=step_size) + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan + + +def load_file(file_path): + with open(file_path, "rb") as file_handle: + file_content = file_handle.read() + if file_content[0:4].decode("utf-8") == "WSxM": + meta_data, image = WsxmReader.parse_file_content(file_content) + elif file_content[0:16].decode("utf-8") == "GWYPGwyContainer": + return gwy_reader.load_file(file_path) + else: + log.info("Invalid header in file {}".format(file_path)) + return None, None + + return meta_data, image + + +if __name__ == "__main__": + file_path = "/home/matthias/FHI/Data/cSiO2_0001_MTI02_H2O_CypherES_170601_CL02_0026_Im2_Series1_Bwd_Dwd_LnSubtr_Eqlz_zm_GWY.stp" + meta_data, image = load_file(file_path) + from matplotlib import pyplot as plt + + plt.imshow(image, cmap="gray") + plt.show() diff --git a/model/data/readers/sxm_reader.py b/model/data/readers/sxm_reader.py new file mode 100644 index 0000000..5c9e58d --- /dev/null +++ b/model/data/readers/sxm_reader.py @@ -0,0 +1,77 @@ +import logging + +import nanonispy as nap +import numpy as np +from skimage.exposure import rescale_intensity + +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + + +def normalize_image(image): + image -= np.nanmin(image) + image /= np.nanmax(image) + return image + + +def read_file_to_scan(file_path): + try: + scan_file = nap.read.Scan(file_path) + except IOError: + log.info('File does not exist.') + return None + scan_information = {"File Path": file_path, **scan_file.header, + } + scan = scan_file_representation.Scan(file_path, scan_information=scan_information) + image_collection_dict = {} + for channel_name, channel_info in scan_file.signals.items(): + for direction, image in channel_info.items(): + scan_range = scan_information["scan_range"] + step_size = [scan_range[0] / len(image[0]), scan_range[1] / len(image)] + if scan_information['scan_dir'] == "up": + image = np.flipud(image) + image_list = [image] + image_collection_key = "{channel_name}_{direction}".format(channel_name=channel_name, + direction=direction) + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + image_collection_dict[image_collection_key] = scan_image_collection.ScanGridImageCollection( + scan=scan, images_2d=image_list, scan_image_information=scan_image_information, step_size=step_size) + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan + + +def read_files_to_scan(file_paths): + file_paths.sort() + scan_information = {"File Paths": file_paths, + } + scan = scan_file_representation.Scan(str(file_paths), scan_information=scan_information) + + image_collection_dict = {} + for file_path in file_paths: + try: + scan_file = nap.read.Scan(file_path) + scan_information = {**scan_information, **scan_file.header} + except IOError: + log.info('File does not exist.') + return None + for channel_name, channel_info in scan_file.signals.items(): + for direction, image in channel_info.items(): + image_class_name = "{channel_name}_{direction}".format(channel_name=channel_name, + direction=direction) + unscaled_img = scan_file.signals[channel_name][direction] + image = rescale_intensity(unscaled_img, 'image', (0, 1)) + + if image_class_name not in image_collection_dict: + log.info("New Image Class {} created from file {}".format(image_class_name, file_path)) + image_collection_dict[image_class_name] = [image] + else: + image_collection_dict[image_class_name].append(image) + + for image_class_name, image_list in image_collection_dict.items(): + scan_image_information = readers_help_functions.get_scan_image_information(image_list) + image_collection_dict[image_class_name] = scan_image_collection.ScanGridImageCollection( + scan=scan, images_2d=image_list, scan_image_information=scan_image_information) + scan.set_image_collection(scan_image_collection_dict=image_collection_dict) + return scan diff --git a/model/data/readers/yolo_label_folder_reader.py b/model/data/readers/yolo_label_folder_reader.py new file mode 100644 index 0000000..c7ff4f5 --- /dev/null +++ b/model/data/readers/yolo_label_folder_reader.py @@ -0,0 +1,45 @@ + + +import os + +import numpy as np + + +def generate_features_classes_from_object_list(scan, object_in_image_list, feature_class_name_list=None): + if feature_class_name_list is None: + feature_class_name_list = [] + for image_number, object_list in enumerate(object_in_image_list): + if image_number == 0: + pass + + +def get_objects_from_labels(file_path): + object_list = [] + with open(file_path) as file: + for line in file.readlines(): + id, x, y, width, height = line.split(" ") + object_list.append( + [int(id), float(x), float(y), float(width), + float(height)]) + object_array = np.asarray(object_list) + return object_array + + +def read_features_from_folder(folder_path, feature_organizer_instance): + objects_in_image_list = [] + + for image_number, file in enumerate(sorted(os.listdir(folder_path))): + if file.split(".")[-1] != "txt": + continue + file_path = os.path.join(folder_path, file) + object_array = get_objects_from_labels(file_path=file_path) + object_center_array = object_array[:, 1:3] + feature_classes = np.unique(object_array[:, 0]) + for feature_class in feature_classes: + feature_organizer_instance.add_feature_class(str(feature_class)) + for feature_class, x, y, *_ in object_array: + feature_class = str(feature_class) + feature_class_index, _ = feature_organizer_instance.get_feature_class_by_string(feature_class) + feature_organizer_instance.auto_add_feature_points(feature_class_index, image_number, [x, y, 0]) + + print(objects_in_image_list) diff --git a/model/data/readers/zip_reader.py b/model/data/readers/zip_reader.py new file mode 100644 index 0000000..a1354d8 --- /dev/null +++ b/model/data/readers/zip_reader.py @@ -0,0 +1,39 @@ +import logging +import zipfile + +import numpy as np + +from model.data.readers import readers_help_functions +from model.data.scan_representation import scan_file_representation, scan_image_collection + +log = logging.getLogger(__name__) + + +def read_file_to_scan(file_path) -> scan_file_representation.Scan or None: + input_zip_file = zipfile.ZipFile(file_path, 'r') + for file in input_zip_file.namelist(): + if file[-5:] == "x.npy": + x_zip_file = input_zip_file.extract(file) + x = np.load(x_zip_file) + elif file[-5:] == "y.npy": + y_zip_file = input_zip_file.extract(file) + y = np.load(y_zip_file) + elif file[-5:] == "z.npy": + z_zip_file = input_zip_file.extract(file) + z = np.load(z_zip_file) + try: + data = np.column_stack((x, y, z)) + except (AttributeError, UnboundLocalError): + log.info("The zip file does not contain the x, y and z values") + return None + cleaned_data = [data] + frequency_sampling = 1 + time_stamps = [0] + scan = scan_file_representation.Scan(file_path=file_path, number_of_bins=100, grid_2d_window="full", + frequency_sampling=frequency_sampling) + scan_image_information = readers_help_functions.get_scan_image_information(cleaned_data, number_of_bins=100) + image_collection_dict = { + "None": scan_image_collection.ScanGridImageCollection(scan=scan, signal_1d_list=cleaned_data, + time_stamps=time_stamps)} + scan.set_image_collection(image_collection_dict) + return scan diff --git a/model/data/readers/zip_scan_file_reader.py b/model/data/readers/zip_scan_file_reader.py new file mode 100644 index 0000000..df32a38 --- /dev/null +++ b/model/data/readers/zip_scan_file_reader.py @@ -0,0 +1,17 @@ +import logging +import pickle +import zipfile + +log = logging.getLogger(__name__) + + +def read_zip_scan_file(file_path): + input_zip_file = zipfile.ZipFile(file_path, 'r') + if 'vsy_object' in input_zip_file.namelist(): + vsy_file_path = input_zip_file.extract('vsy_object') + with open(vsy_file_path, 'rb') as vsy_file: + scan = pickle.load(vsy_file) + return scan + else: + log.info("Not implemented yet, zip file needs a vsy object") + return None diff --git a/model/data/scan_organizier.py b/model/data/scan_organizier.py new file mode 100644 index 0000000..83bf31f --- /dev/null +++ b/model/data/scan_organizier.py @@ -0,0 +1,23 @@ +from model.data.scan_representation import scan_file_representation + + +class ScanOrganizer: + scans = list() + current_scan = None # type: scan_file_representation.Scan + scan_id = 0 + + @classmethod + def add_scan(cls, scan): + scan.id = cls.scan_id + cls.scans.append(scan) + cls.scan_id += 1 + + @classmethod + def set_current_scan(cls, scan): + cls.current_scan = scan + + @classmethod + def get_scan_index_by_string(cls, scan_name): + for i in range(len(cls.scans)): + if str(cls.scans[i]) == scan_name: + return i diff --git a/model/data/scan_representation/__init__.py b/model/data/scan_representation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/data/scan_representation/scan_file_representation.py b/model/data/scan_representation/scan_file_representation.py new file mode 100644 index 0000000..e06a436 --- /dev/null +++ b/model/data/scan_representation/scan_file_representation.py @@ -0,0 +1,339 @@ +import logging +import os +import subprocess +import typing +from datetime import datetime + +import numpy as np + +from model.data.feature import feature_organizer +from model.data.scan_representation import scan_image_collection +from model.visualization.visualization_methods.histogram_2d_visualization import standard_visualization +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + +log = logging.getLogger(__name__) + + +class Scan: + def __init__(self, file_path, number_of_bins=None, grid_2d_window=None, grid_2d_method="histogram", + frequency_sampling=None, scan_information=None, plotable_dict=None): + self.frequency_sampling = frequency_sampling + if scan_information is None: + self.scan_information = {} + else: + self.scan_information = scan_information + self.id = None + self.number_of_bins = number_of_bins + self.grid_2d_window = grid_2d_window + self.grid_2d_method = grid_2d_method + self.scan_image_collection_dict: typing.Dict[scan_image_collection.ScanGridImageCollection] = None + self.nan_color = (0, 0, 0, 0) + self.current_image_class = None + self.interactor_style = "Image" + self.pre_visualization_parameters = None + self.visualization_logarithmic = True + self.show_original_image = False + self.z_scale = 1 + self.contrast_method = 'percentile' + self.contrast_min, self.contrast_max = 1, 99 + self.file_path = file_path + self.background_color = [0.5, 0.5, 0.5, 1] + self.git_hash = self.get_git_hash() + if plotable_dict is not None: + self._plotable_dict = plotable_dict + self.pre_visualization_method: AbstractVisuzalization = standard_visualization.StandardVisualization() + self._color_bar_dict = {} + + @property + def plotable_dict(self): + try: + return self._plotable_dict + except AttributeError: + self._plotable_dict = {} + return self._plotable_dict + + @plotable_dict.setter + def plotable_dict(self, plotable_dict): + self._plotable_dict = plotable_dict + + @property + def image_id_list(self): + return self.scan_image_collection_dict[self.current_image_class].image_id_list + + @property + def color_bar_dict(self): + try: + return self._color_bar_dict + except AttributeError: + self._color_bar_dict = {} + return self._color_bar_dict + + def set_image_collection(self, scan_image_collection_dict): + self.scan_image_collection_dict = scan_image_collection_dict + self.current_image_class = list(self.scan_image_collection_dict.keys())[0] + + @property + def images(self): + if self.show_original_image: + return self.scan_image_collection_dict[self.current_image_class].scan_images_2d.images_original + else: + return self.scan_image_collection_dict[self.current_image_class].scan_images_2d.images + + @property + def density_arrays(self): + return self.scan_image_collection_dict[self.current_image_class].scan_images_2d.density_array_list + + @property + def data_range(self): + x_range = self.scan_image_collection_dict[self.current_image_class].scan_images_2d.x_range + y_range = self.scan_image_collection_dict[self.current_image_class].scan_images_2d.y_range + return [x_range, y_range] + + @property + def feature_organizer_instance(self) -> feature_organizer.FeatureOrganizer: + return self.scan_image_collection_dict[self.current_image_class].feature_organizer_instance + + def get_feature_classes(self): + return self.scan_image_collection_dict[self.current_image_class].get_feature_classes() + + @property + def images_1d(self): + if self.show_original_image: + return self.scan_image_collection_dict[self.current_image_class].scan_images_1d.images_original + else: + return self.scan_image_collection_dict[self.current_image_class].scan_images_1d.images + + @property + def filter_1d_organizer(self): + return self.scan_image_collection_dict[self.current_image_class].filter_1d_organizer + + @filter_1d_organizer.setter + def filter_1d_organizer(self, filter_organizer): + self.scan_image_collection_dict[self.current_image_class].filter_1d_organizer = filter_organizer + + @property + def filter_1d_full_signal_organizer(self): + return self.scan_image_collection_dict[self.current_image_class].filter_1d_full_signal_organizer + + @filter_1d_full_signal_organizer.setter + def filter_1d_full_signal_organizer(self, filter_organizer): + self.scan_image_collection_dict[self.current_image_class].filter_1d_full_signal_organizer = filter_organizer + + @property + def filter_2d_organizer(self): + return self.scan_image_collection_dict[self.current_image_class].filter_2d_organizer + + @filter_2d_organizer.setter + def filter_2d_organizer(self, filter_organizer): + self.scan_image_collection_dict[self.current_image_class].filter_2d_organizer = filter_organizer + + @property + def image_filtered_1d_full(self): + return self.scan_image_collection_dict[self.current_image_class].scan_images_1d.image_filtered_1d_full + + @property + def images_1d_original(self): + return self.scan_image_collection_dict[self.current_image_class].scan_images_1d.images_original + + @property + def images_2d_original(self): + return self.scan_image_collection_dict[self.current_image_class].scan_images_2d.images_original + + @property + def image_classes(self): + return list(self.scan_image_collection_dict.keys()) + + @property + def time_stamps(self): + return self.scan_image_collection_dict[self.current_image_class].scan_images_2d.time_stamps + + def change_current_image_class(self, image_class): + if image_class in self.image_classes: + self.current_image_class = image_class + else: + self.current_image_class = list(self.scan_image_collection_dict.keys())[0] + + @property + def image_maximum_index(self): + return len(self.images) - 1 + + def get_min_amplitude(self, image_number): + image = self.images[image_number] + image = np.nan_to_num(image) + f = np.fft.fft2(image) + magnitude_spectrum = np.abs(f) + return np.min(magnitude_spectrum) + + def get_max_amplitude(self, image_number): + image = self.images[image_number] + image = np.nan_to_num(image) + f = np.fft.fft2(image) + magnitude_spectrum = np.abs(f) + return np.max(magnitude_spectrum) + + def apply_1d_filters(self, apply_full_signal_filters=False): + self.scan_image_collection_dict[self.current_image_class].apply_1d_filters(apply_full_signal_filters) + + def apply_2d_filters(self): + self.scan_image_collection_dict[self.current_image_class].apply_2d_filters() + + def apply_drift_correction_vectors(self, drift_vectors, number_of_bins=None): + self.scan_image_collection_dict[self.current_image_class].apply_drift_correction_vector(drift_vectors, + number_of_bins=number_of_bins) + + def __str__(self): + return str(self.id) + " ({file_path})".format(file_path=self.file_path) + + def add_filter_by_index(self, filter_index, filter_type, parameters=None): + if parameters is None: + parameters = list() + if filter_type == "1d": + self.filter_1d_organizer.add_filter_by_index(filter_index, parameters=parameters) + elif filter_type == "2d": + self.filter_2d_organizer.add_filter_by_index(filter_index, parameters=parameters) + elif filter_type == "1d_full_signal": + self.filter_1d_full_signal_organizer.add_filter_by_index(filter_index, parameters=parameters) + + def remove_filter(self, filter_index, filter_type): + if filter_type == "1d": + self.filter_1d_organizer.remove_filter(filter_index) + elif filter_type == "1d_full_signal": + self.filter_1d_full_signal_organizer.remove_filter(filter_index) + elif filter_type == "2d": + self.filter_2d_organizer.remove_filter(filter_index) + + @staticmethod + def binary_write(image, output_filename, fmt='f'): + output_file = open(output_filename, 'wb') + float_array = np.array(fmt, image.ravel()) + float_array.tofile(output_file) + output_file.close() + + def get_filters_as_string(self): + filter_string = "" + try: + filter_string += ("__1D Full Signal Filters__\n") + filter_string += str(self.filter_1d_full_signal_organizer) + filter_string += "__1D Filters__\n" + filter_string += str(self.filter_1d_organizer) + except AttributeError: + pass + filter_string += "__2D Filters__\n" + filter_string += r'{}'.format(self.filter_2d_organizer) + return filter_string + + def export_filter_list_to_text_file(self, file_name=None, filter_log_folder="filter_logs"): + if filter_log_folder is not None and not os.path.exists(filter_log_folder): + os.mkdir(filter_log_folder) + if file_name is None: + scan_file_name = self.file_path.split("/")[-1].split(".")[0] # Get the Filename which created the scan + file_name = "{scan_file_name}_{time_stamp}.log".format(scan_file_name=scan_file_name, + time_stamp=datetime.now().strftime( + "%Y_%m_%d_%H_%M_%S")) + if filter_log_folder is not None: + file_path = os.path.join(filter_log_folder, file_name) + else: + file_path = file_name + with open(file_path, "w") as f: + filter_string = self.get_filters_as_string() + f.write(filter_string) + log.info("Filter logs were written to: {folder_name}/{file_name}".format(folder_name=filter_log_folder, + file_name=file_name)) + + @property + def step_size(self): + return self.scan_image_collection_dict[self.current_image_class].step_size + + @step_size.setter + def step_size(self, step_size): + self.scan_image_collection_dict[self.current_image_class].step_size = step_size + + @staticmethod + def get_git_hash(): + try: + git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']) + hash_string = git_hash.strip().decode('ascii') + except: + hash_string = "No git rep found" + return hash_string + + def get_saasmi(self, image_number): + return self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.get_saasmi(image_number) + + def saasmi_rag_remove_edge(self, image_number, key): + self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.saasmi_rag_remove_edge(image_number=image_number, key=key) + + def saasmi_rag_add_edge(self, image_number, key): + self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.saasmi_rag_add_edge(image_number=image_number, key=key) + + def saasmi_rag_remove_all_edges_from_node(self, image_number, node_key): + self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.saasmi_rag_remove_all_edges_from_node(image_number=image_number, + node_key=node_key) + + def remove_image_class(self, image_class): + del self.scan_image_collection_dict[image_class] + + def get_full_1d_signal(self): + return self.scan_image_collection_dict[ + self.current_image_class].get_full_1d_signal() + + def saasmi_rag_remove_node(self, image_number, node_id): + return self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.saasmi_rag_remove_node(image_number=image_number, node_id=node_id) + + def saasmi_add_node(self, image_number, saasmi_position): + return self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.saasmi_rag_add_node(image_number=image_number, + saasmi_position=saasmi_position) + + def saasmi_get_atom_network(self, image_number, atom_network_type="silicon_oxygen"): + return self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.get_atom_network(image_number, atom_network_type) + + def saasmi_recreate_atom_network(self, image_number, atom_network_type): + return self.scan_image_collection_dict[ + self.current_image_class].scan_images_2d.recreate_atom_network(image_number, atom_network_type) + + def get_scan_image_information(self): + return self.scan_image_collection_dict[ + self.current_image_class].scan_image_information + + def get_measuring_point_information(self, image_number, measuring_points, middle_point, point_index): + return self.scan_image_collection_dict[self.current_image_class].get_measuring_point_information(image_number, + measuring_points, + middle_point, + point_index) + + @property + def ideal_wave_forms(self): + return self.scan_image_collection_dict[self.current_image_class].ideal_wave_forms + + def get_image_scan_direction(self, image_number): + self.scan_image_collection_dict[self.current_image_class].get_image_scan_direction(image_number) + + def add_point_to_feature_grid_list(self, point): + self.scan_image_collection_dict[self.current_image_class].add_point_to_feature_grid_list(point) + + def remove_point_to_feature_grid_list(self, point): + self.scan_image_collection_dict[self.current_image_class].remove_point_to_feature_grid_list(point) + + def get_feature_grid_point_list(self): + return self.scan_image_collection_dict[self.current_image_class].feature_grid_point_list + + def set_feature_grid_visualization_settings(self, feature_grid_visualization_settings): + self.scan_image_collection_dict[self.current_image_class].set_feature_grid_visualization_settings( + feature_grid_visualization_settings) + + def get_feature_grid_visualization_settings(self): + return self.scan_image_collection_dict[self.current_image_class].feature_grid_visualization_settings + + def get_velocity_array(self, image_number, ideal): + return self.scan_image_collection_dict[self.current_image_class].get_velocity_array(image_number, ideal) + + def get_angular_velocity_array(self, image_number,middle_point, point_index, ideal): + return self.scan_image_collection_dict[self.current_image_class].get_angular_velocity_array(image_number,middle_point, point_index, ideal) diff --git a/model/data/scan_representation/scan_image_collection.py b/model/data/scan_representation/scan_image_collection.py new file mode 100644 index 0000000..18e56a6 --- /dev/null +++ b/model/data/scan_representation/scan_image_collection.py @@ -0,0 +1,440 @@ +import copy + +import numpy as np +import pandas as pd + +from model.data.feature import feature_organizer +from model.data.scan_representation import scan_images_data_1d, scan_images_grid_data_2d + + +class ScanGridImageCollection: + def __init__(self, scan, signal_1d_list=None, images_2d=None, time_stamps=None, step_size=None, + scan_image_information=None, ideal_wave_forms=None, image_id_list=None): + self._ideal_wave_forms = ideal_wave_forms + self._feature_grid_point_list = [] + self._feature_grid_visualization_settings = None + self._image_id_list = image_id_list + if scan_image_information is None: + self.scan_image_information = {} + else: + self.scan_image_information = scan_image_information + + self.scan_file_representation_instance = scan + if signal_1d_list is not None: + self._generade_from_1d_signal(signal_1d_list=signal_1d_list, time_stamps=time_stamps) + elif images_2d is not None: + self._generate_from_2d_image(images_2d=images_2d, time_stamps=time_stamps, step_size=step_size) + self.time_stamps = time_stamps + self.feature_organizer_instance = feature_organizer.FeatureOrganizer( + self.get_scan_file_representation_instance(), + len(self.images_2d)) + if self.scan_images_2d.step_size is not None: + self.scan_image_information["Distance between Pixels"] = self.scan_images_2d.step_size + + @property + def feature_grid_point_list(self): + try: + return self._feature_grid_point_list + except AttributeError: + self._feature_grid_point_list = [] + return self._feature_grid_point_list + + def set_feature_grid_visualization_settings(self, feature_grid_visualization_settings): + self._feature_grid_visualization_settings = feature_grid_visualization_settings + + @property + def feature_grid_visualization_settings(self): + try: + return self._feature_grid_visualization_settings + except AttributeError: + self._feature_grid_visualization_settings = None + return self._feature_grid_visualization_settings + + def _generade_from_1d_signal(self, signal_1d_list, time_stamps, number_of_bins=None): + self.data_range = self.__generate_data_range(signal_1d_list) + self.scan_images_1d = scan_images_data_1d.ScanImagesData1D(image=signal_1d_list, + scan_grid=self.get_scan_file_representation_instance(), + scan_image_collection=self) + self.scan_images_2d = scan_images_grid_data_2d.ScanImagesGridData2D(image_1d=signal_1d_list, + scan=self.get_scan_file_representation_instance(), + scan_image_collection=self, + time_stamps=time_stamps, + number_of_bins=number_of_bins) + + def _generate_from_2d_image(self, images_2d, time_stamps, step_size=None): + self.scan_images_1d = None + self.scan_images_2d = scan_images_grid_data_2d.ScanImagesGridData2D(image_2d=images_2d, + scan=self.get_scan_file_representation_instance(), + scan_image_collection=self, + time_stamps=time_stamps, + step_size=step_size) + + @property + def filter_1d_full_signal_organizer(self): + return self.scan_images_1d.filter_full_signal_organizer + + @filter_1d_full_signal_organizer.setter + def filter_1d_full_signal_organizer(self, filter_organizer): + self.scan_images_1d.filter_full_signal_organizer = filter_organizer + + @property + def filter_1d_organizer(self): + return self.scan_images_1d.filter_organizer + + @filter_1d_organizer.setter + def filter_1d_organizer(self, filter_organizer): + self.scan_images_1d.filter_organizer = filter_organizer + + @property + def filter_2d_organizer(self): + return self.scan_images_2d.filter_organizer + + @filter_2d_organizer.setter + def filter_2d_organizer(self, filter_organizer): + self.scan_images_2d.filter_organizer = filter_organizer + + @property + def step_size(self): + return self.scan_images_2d.step_size + + @step_size.setter + def step_size(self, step_size): + self.scan_images_2d.step_size = step_size + + @property + def image_id_list(self): + try: + return self._image_id_list + except AttributeError: + self._image_id_list = None + return self._image_id_list + + @property + def images_2d(self): + return self.scan_images_2d.images + + @property + def images_2d_original(self): + return self.scan_images_2d.images_original + + @property + def images_1d(self): + if self.scan_images_1d is None: + return None + else: + return self.scan_images_1d.images + + @property + def image_filtered_1d_full(self): + return self.scan_images_1d.image_filtered_1d_full + + @property + def images_1d_original(self): + return self.scan_images_1d.images_original + + def apply_1d_filters(self, apply_full_signal_filters=False, force_execute=False): + if apply_full_signal_filters is True: + filter_1d_full_applied = self.scan_images_1d.apply_1d_full_signal_filters(force_execute=force_execute) + force_execute = filter_1d_full_applied + filter_1d_image_applied = self.scan_images_1d.apply_1d_image_filters(force_execute=force_execute) + force_execute = filter_1d_image_applied + if filter_1d_image_applied: + self.scan_images_2d.update_original_image(self.scan_images_1d.images) + self.scan_images_2d.apply_filters(force_execute=force_execute) + + def apply_2d_filters(self): + self.scan_images_2d.apply_filters() + + def __generate_data_range(self, image_1d_signal): + data_range = pd.DataFrame(columns=['x_min', "x_max", "y_min", "y_max", "z_min", "z_max"]) + for image_number in range(len(image_1d_signal)): + range_dict = dict() + range_dict["x_min"] = image_1d_signal[image_number][:, 0].min() + range_dict["x_max"] = image_1d_signal[image_number][:, 0].max() + range_dict["x_range"] = range_dict["x_max"] - range_dict["x_min"] + range_dict["y_min"] = image_1d_signal[image_number][:, 1].min() + range_dict["y_max"] = image_1d_signal[image_number][:, 1].max() + range_dict["y_range"] = range_dict["y_max"] - range_dict["y_min"] + range_dict["z_min"] = image_1d_signal[image_number][:, 2].min() + range_dict["z_max"] = image_1d_signal[image_number][:, 2].max() + range_dict["z_range"] = range_dict["z_max"] - range_dict["z_min"] + data_range = data_range.append(range_dict, ignore_index=True) + return data_range + + def apply_drift_correction_vector(self, drift_vectors, number_of_bins=None, apply_feature_drift_correction=True): + drift_vectors = np.array(drift_vectors) + if self.scan_images_1d is not None: + self.apply_drift_correction_1d_signal(drift_vectors, number_of_bins=number_of_bins, + apply_feature_drift_correction=apply_feature_drift_correction, ) + else: + self.apply_drift_correction_2d_image(drift_vectors) + if apply_feature_drift_correction: + self.scan_images_1d = None + self.apply_drift_correction_2d_to_features(drift_vectors) + + def get_additional_number_of_bins(self, drift_vector): + number_of_bins_x = max(drift_vector[:, 0]) - min(drift_vector[:, 0]) + number_of_bins_y = max(drift_vector[:, 1]) - min(drift_vector[:, 1]) + return int(max(number_of_bins_x, number_of_bins_y)) + 1 + + def apply_drift_correction_1d_signal(self, drift_vectors, number_of_bins=None, apply_feature_drift_correction=True): + signal_1d = self.images_1d_original + drift_vectors_1d = copy.copy(drift_vectors) + drift_vectors_1d[:, 0] = drift_vectors_1d[:, 0] * self.step_size[0] + drift_vectors_1d[:, 1] = drift_vectors_1d[:, 1] * self.step_size[1] + for image_number, vector in enumerate(drift_vectors_1d): + signal_1d[image_number][:, 0] -= vector[1] + signal_1d[image_number][:, 1] -= vector[0] + time_stamps = self.scan_images_2d.time_stamps + prev_step_size = copy.copy(self.step_size) + new_number_of_bins = number_of_bins + self.get_additional_number_of_bins(drift_vectors) + self._generade_from_1d_signal(signal_1d, time_stamps, number_of_bins=new_number_of_bins) + after_step_size = copy.copy(self.step_size) + if apply_feature_drift_correction: + x_scale = prev_step_size[0] / after_step_size[0] + y_scale = prev_step_size[1] / after_step_size[1] + drift_vectors = self.__generate_1d_drift_vectors(drift_vectors, x_scale, y_scale) + self.apply_drift_correction_1d_to_features(drift_vectors, x_scale, y_scale) + + def __generate_1d_drift_vectors(self, drift_vectors, x_scale, y_scale): + first_image = self.images_2d_original[0] + image_not_nan_indicies = np.argwhere(~np.isnan(first_image)) + lowest_y = image_not_nan_indicies[:, 0].min() + lowest_x = image_not_nan_indicies[:, 1].min() + for drift_vector in drift_vectors: + drift_vector[0] = drift_vector[0] * x_scale - lowest_x + drift_vector[1] = drift_vector[1] * y_scale - lowest_y + return drift_vectors + + def apply_drift_correction_1d_to_features(self, drift_vectors, x_scale, y_scale): + for feautre_class in self.feature_organizer_instance.feature_classes: + for feature_index in feautre_class.features: + for image_number, feature_points in feature_index.feature_point_positions.items(): + for feature_point_index in range(len(feature_points)): + try: + feature_points[feature_point_index][0] *= x_scale + feature_points[feature_point_index][1] *= y_scale + feature_points[feature_point_index][0] -= drift_vectors[image_number][0] + feature_points[feature_point_index][1] -= drift_vectors[image_number][1] + except IndexError: + pass + + def apply_drift_correction_2d_image(self, drift_vectors): + image_2d = self.images_2d_original + image_list = list() + for image_number, vector in enumerate(drift_vectors): + image = image_2d[image_number].copy() + try: + correction_x_direction = int(round(vector[0], 0)) + correction_y_direction = int(round(vector[1], 0)) + except: + image_list.append(image) + continue + if correction_y_direction > 0: + image[:correction_y_direction, :] = np.nan + elif correction_y_direction < 0: + image[correction_y_direction:, :] = np.nan + if correction_x_direction > 0: + image[:, :correction_x_direction] = np.nan + elif correction_y_direction < 0: + image[:, correction_x_direction:] = np.nan + image = np.roll(image, -correction_y_direction, axis=0) + image = np.roll(image, -correction_x_direction, axis=1) + image_list.append(image) + time_stamps = self.scan_images_2d.time_stamps + self._generate_from_2d_image(images_2d=image_list, time_stamps=time_stamps) + + def apply_drift_correction_2d_to_features(self, drift_vectors): + for feautre_class in self.feature_organizer_instance.feature_classes: + for feature_index in feautre_class.features: + for image_number, feature_points in feature_index.feature_point_positions.items(): + for feature_point_index in range(len(feature_points)): + try: + feature_points[feature_point_index][0] -= drift_vectors[image_number][0] + feature_points[feature_point_index][1] -= drift_vectors[image_number][1] + except IndexError: + pass + + def get_full_1d_signal(self): + return self.scan_images_1d.full_signal + + def get_measuring_point_information(self, image_number, measuring_points, middle_point, point_index): + signal_1d = copy.copy(self.images_1d[image_number]) + if measuring_points is None: + indices = np.arange(signal_1d.shape[0]) + else: + x_indices = np.in1d(signal_1d[:, 0], measuring_points[:, 0]).nonzero() + y_indices = np.in1d(signal_1d[:, 1], measuring_points[:, 1]).nonzero() + indices = np.intersect1d(x_indices, y_indices) + return self.get_point_information_by_indicies(image_number=image_number, signal_1d=signal_1d, indices=indices, + middle_point=middle_point, point_index=point_index) + + def get_point_information_by_indicies(self, image_number, signal_1d, indices, middle_point, point_index): + point_list = signal_1d[indices, :] + velocity = self.get_velocity_array(image_number, ideal=False) + angular_velocity = self.get_angular_velocity_array(image_number, middle_point, point_index, ideal=False) + information_dict = {} + information_dict["number_of_points"] = len(indices) + information_dict["min_intensity"] = point_list[:, 2].min() + information_dict["max_intensity"] = point_list[:, 2].max() + information_dict["mean_intensity"] = point_list[:, 2].mean() + information_dict["min_velocity"] = np.min(abs(velocity[indices])) + information_dict["max_velocity"] = np.max(abs(velocity[indices])) + information_dict["velocity_acceleration"] = self.get_acceleration_from_velocity_array(velocity, image_number) + information_dict["min_angular_velocity"] = np.min(abs(angular_velocity[indices])) + information_dict["max_angular_velocity"] = np.max(abs(angular_velocity[indices])) + information_dict["velocity_angle_acceleration"] = self.get_acceleration_from_velocity_array(angular_velocity, + image_number) + information_dict["measuring_time_window"] = self.__get_measuring_time_window(image_number, indices) + information_dict["measuring_time_window_delta"] = information_dict["measuring_time_window"][1] - \ + information_dict["measuring_time_window"][0] + time_information_dict = self.__get_measuring_points_timing_information(image_number=image_number, + velocity_array=velocity, + angular_velocity_array=angular_velocity, + signal_1d=signal_1d, + indices=indices) + information_dict["time_information"] = time_information_dict + return information_dict + + def get_acceleration_from_velocity_array(self, velocity_array, image_number=None): + time_stamp_delta = self.get_time_stamp_delta(image_number) / len(velocity_array) + time_array = np.arange(len(velocity_array)) * time_stamp_delta + fit = np.polyfit(time_array, velocity_array, 1) + return fit[0] + + def __get_measuring_time_window(self, image_number, indices): + start_time = self.time_stamps[image_number] + sample_frequency = self.get_scan_file_representation_instance().frequency_sampling + indices_time_stamps = indices / sample_frequency + indices_time_stamps += start_time + return [start_time + indices_time_stamps[0], start_time + indices_time_stamps[-1]] + + def __get_measuring_points_timing_information(self, image_number, velocity_array, angular_velocity_array, signal_1d, + indices): + start_time = self.time_stamps[image_number] + sample_frequency = self.get_scan_file_representation_instance().frequency_sampling + try: + end_time = start_time + len(signal_1d) / sample_frequency + except IndexError: + end_time = None + indices_time_stamps = indices / sample_frequency + indices_time_stamps += start_time + time_information = np.column_stack([indices_time_stamps, signal_1d[indices, 2]]) + selected_velocity_array = np.column_stack( + [indices_time_stamps, + velocity_array[indices]]) + selected_angular_velocity_array = np.column_stack( + [indices_time_stamps, + angular_velocity_array[indices]]) + time_information_dict = {"image_start_time": start_time, + "image_end_time": end_time, + "sample_frequency": sample_frequency, + "time_stamp_value_array": time_information, + "velocity_array": selected_velocity_array, + "angular_velocity_array": selected_angular_velocity_array} + return time_information_dict + + def get_velocity_array(self, image_number, ideal): + if ideal is False: + signal_1d = copy.copy(self.images_1d[image_number]) + else: + signal_1d = copy.copy(self.ideal_wave_forms[0]) + if signal_1d is None: + return None + velocity_array_deltas = signal_1d[1:, :2] - signal_1d[:-1, :2] + dist = np.sqrt(np.einsum('ij,ij->i', velocity_array_deltas, velocity_array_deltas)) + dist = np.append(dist, dist[-1]) + time_stamp_delta = self.get_time_stamp_delta(image_number) + velocity = dist / (time_stamp_delta / len(signal_1d)) + return velocity + + @staticmethod + def get_angle_array(data_deltas): + angle_array = np.arctan2(data_deltas[:, 1], data_deltas[:, 0]) + angle_array = np.rad2deg(angle_array) + return angle_array + + @staticmethod + def get_angular_velocity(angle_array): + angular_velocity = angle_array[1:] - angle_array[:-1] + if np.sum(angular_velocity > 0) > np.sum(angular_velocity < 0): + angular_velocity[angular_velocity < 0] += 360 + else: + angular_velocity[angular_velocity > 0] -= 360 + over_180_deg = angular_velocity > 180 + under_180_deg = angular_velocity < -180 + angular_velocity[over_180_deg] = -360 + angular_velocity[over_180_deg] + angular_velocity[under_180_deg] = 360 + angular_velocity[under_180_deg] + return angular_velocity + + + # @staticmethod + # def get_angular_velocity(angle_array): + # angular_velocity = angle_array[1:] - angle_array[:-1] + # over_180_deg = angular_velocity > 180 + # under_180_deg = angular_velocity < -180 + # angular_velocity[over_180_deg] = -360 + angular_velocity[over_180_deg] + # angular_velocity[under_180_deg] = 360 + angular_velocity[under_180_deg] + # return angular_velocity + + @staticmethod + def get_center(data): + center_x = (np.max(data[:, 0] - np.min(data[:, 0]))) / 2 + np.min(data[:, 0]) + center_y = (np.max(data[:, 1] - np.min(data[:, 1]))) / 2 + np.min(data[:, 1]) + return [center_x, center_y] + + def get_angular_velocity_array(self, image_number, middle_point, point_index, ideal): + if ideal is False: + signal_1d = copy.copy(self.images_1d[image_number]) + else: + signal_1d = copy.copy(self.ideal_wave_forms[0]) + if signal_1d is None: + return None + if middle_point is True: + center_point = self.get_center(signal_1d) + else: + center_point = signal_1d[point_index, :2] + data_deltas = signal_1d[:, :2] - center_point + angle_array = self.get_angle_array(data_deltas) + # plt.scatter(data[:,0], data[:,1]) + angular_velocity = self.get_angular_velocity(angle_array) + angular_velocity = np.append(angular_velocity, angular_velocity[-1]) + return angular_velocity + + def get_time_stamp_delta(self, image_number): + try: + time_stamp_delta = self.time_stamps[image_number + 1] - self.time_stamps[image_number] + except IndexError: + time_stamp_delta = self.time_stamps[image_number] - self.time_stamps[image_number - 1] + return time_stamp_delta + + def get_scan_file_representation_instance(self): + try: + return self.scan_file_representation_instance + except AttributeError: + pass + try: + self.scan_file_representation_instance = self.scan_grid + return self.scan_file_representation_instance + except AttributeError: + pass + + @property + def ideal_wave_forms(self): + try: + return self._ideal_wave_forms + except AttributeError: + self._ideal_wave_forms = None + return self._ideal_wave_forms + + def add_point_to_feature_grid_list(self, point): + self.feature_grid_point_list.append(point) + + def remove_point_to_feature_grid_list(self, point): + point_list_array = np.asarray(self.feature_grid_point_list) + dist_2 = np.sum((point_list_array - point) ** 2, axis=1) + index = int(np.argmin(dist_2)) + self.feature_grid_point_list.pop(index) + + def get_feature_lines(self): + pass \ No newline at end of file diff --git a/model/data/scan_representation/scan_images_data_1d.py b/model/data/scan_representation/scan_images_data_1d.py new file mode 100644 index 0000000..cfc28e5 --- /dev/null +++ b/model/data/scan_representation/scan_images_data_1d.py @@ -0,0 +1,88 @@ +import copy +import multiprocessing + +import numpy as np + +from model import config +from model.filter import filter_1d_organizer + + +class ScanImagesData1D: + def __init__(self, image, scan_grid, scan_image_collection): + self.scan = scan_grid + self.scan_image_collection = scan_image_collection + self.original_image_list = image.copy() + self.image_filtered_1d_full = image.copy() + self.image_filtered = image.copy() + self.filter_organizer = filter_1d_organizer.Filter1DOrganizer() + self.filter_full_signal_organizer = filter_1d_organizer.Filter1DOrganizer() + self.image_backup = image.copy() + self.filter_organizer_hash = self.hash_filter_organizer(None) + self.filter_full_signal_organizer_hash = self.hash_filter_organizer(None) + + @property + def images(self): + return self.image_filtered + + @property + def images_original(self): + return self.original_image_list + + def apply_filters(self, force_execute=False): + full_signal_applied = self.apply_1d_full_signal_filters(force_execute=force_execute) + if full_signal_applied: + force_execute = True + image_filter_applied = self.apply_1d_image_filters(force_execute=force_execute) + return full_signal_applied or image_filter_applied + + def apply_1d_full_signal_filters(self, force_execute): + if self.hash_filter_organizer( + self.filter_full_signal_organizer) != self.filter_full_signal_organizer_hash or force_execute: + self.image_filtered_1d_full = copy.deepcopy(self.original_image_list) + if len(self.filter_full_signal_organizer.filters) > 0: + self.__apply_filters_full_signal_without_multiprocessing() + self.filter_full_signal_organizer_hash = self.hash_filter_organizer(self.filter_full_signal_organizer) + return True + + def apply_1d_image_filters(self, force_execute=False): + if self.hash_filter_organizer(self.filter_organizer) != self.filter_organizer_hash or force_execute: + self.image_filtered = copy.deepcopy(self.image_filtered_1d_full) + if len(self.filter_organizer.filters) > 0: + if not config.config["program_settings"]['filter_1d_multiprocessing']: + self.__apply_filters_without_multiprocessing() + else: + self.__apply_filters_with_multiprocessing() + self.filter_organizer_hash = self.hash_filter_organizer(self.filter_organizer) + return True + + def __apply_filters_with_multiprocessing(self): + pool = multiprocessing.Pool(config.config["program_settings"]["filter_1d_concurrency"]) + input_list = self.image_filtered + return_list = pool.map(self.__apply_filters_to_image, input_list) + for i in range(len(self.image_filtered)): + self.image_filtered[i] = return_list[i] + + def __apply_filters_without_multiprocessing(self): + for i in range(len(self.image_filtered)): + self.image_filtered[i] = self.__apply_filters_to_image( + self.image_filtered[i].copy()) + + @staticmethod + def hash_filter_organizer(filter_organizer): + if filter_organizer is None: + return str([]) + return str(filter_organizer.filters) + + def __apply_filters_to_image(self, image): + return self.filter_organizer.get_filtered_image(image) + + def __apply_filters_full_signal_without_multiprocessing(self): + full_signal_image = self.full_signal + number_of_image = len(self.original_image_list) + full_signal_filtered = self.filter_full_signal_organizer.get_filtered_image(full_signal_image) + self.image_filtered_1d_full = np.vsplit(full_signal_filtered, number_of_image) + + @property + def full_signal(self): + full_1d_signal = np.concatenate(self.image_filtered_1d_full, axis=0) + return full_1d_signal diff --git a/model/data/scan_representation/scan_images_grid_data_2d.py b/model/data/scan_representation/scan_images_grid_data_2d.py new file mode 100644 index 0000000..a6a02de --- /dev/null +++ b/model/data/scan_representation/scan_images_grid_data_2d.py @@ -0,0 +1,357 @@ +import copy +import math +import multiprocessing + +import cv2 +import numpy as np +from scipy import interpolate +from skimage.morphology import convex_hull_image +from skimage.segmentation import flood_fill + +from model import config +from model.filter import filter_2d_organizer +from model.saasmi_utils import saasmi + +TO_GRID_METHOD = ["histogram", "histogram_inpaint", "interpolation_nearest", "interpolation_linear", + "interpolation_cubic"] + + +def flood_fill_frame(image, fill_value=np.nan): + image_shape = image.shape + x_max_index = image_shape[0] - 1 + y_max_index = image_shape[1] - 1 + image = flood_fill(image, (0, 0), np.nan) + image = flood_fill(image, (0, y_max_index), np.nan) + image = flood_fill(image, (x_max_index, 0), np.nan) + image = flood_fill(image, (x_max_index, y_max_index), np.nan) + return image + + +def repair_nan_inside_of_image(image): + nan_indices = np.where(np.isnan(image)) + x_indices, y_indices = nan_indices + for x, y in zip(x_indices, y_indices): + try: + left_neighbor = image[x, y - 1] + right_neighbor = image[x, y + 1] + if not np.isnan(left_neighbor) and not np.isnan(right_neighbor): + image[x, y] = (left_neighbor + right_neighbor) / 2 + continue + except IndexError: + pass + + try: + bottom_neighbor = image[x - 1, y] + top_neighbor = image[x + 1, y] + if not np.isnan(bottom_neighbor) and not np.isnan(top_neighbor): + image[x, y] = (bottom_neighbor + top_neighbor) / 2 + except IndexError: + pass + return image + + +def get_convex_hull(image): + convex_hull = convex_hull_image(image) + return convex_hull + + +def get_square_inside_of_circle(image): + number_of_bins = image.shape[0] + real_square_length = math.sqrt(number_of_bins ** 2 / 2) + array = image[ + int((number_of_bins - real_square_length) / 2):int( + (number_of_bins + real_square_length) / 2), + int((number_of_bins - real_square_length) / 2):int( + (number_of_bins + real_square_length) / 2)] + return array + + +def fix_image_with_inpaint(image): + outside_mask = get_convex_hull(image).astype("uint8") + image_mask = np.bitwise_and(np.isnan(image), outside_mask) + # image_repaired = repair_nan_inside_of_image(image) + image_repaired = image + image_repaired = np.nan_to_num(image_repaired) + image_fixed = cv2.inpaint(image_repaired.astype(np.float32), image_mask, 2, cv2.INPAINT_NS) + image_fixed = flood_fill_frame(image_fixed, fill_value=np.nan) + return image_fixed + + +class ScanImagesGridData2D: + def __init__(self, scan, scan_image_collection, image_1d=None, image_2d=None, time_stamps=None, step_size=None, + number_of_bins=None): + self.to_grid_method = scan.grid_2d_method + self.scan = scan + self.scan_image_collection = scan_image_collection + self.filter_organizer_hash = self.hash_filter_organizer(None) + self.time_stamps = time_stamps + self.saasmi_dict = {} + self.filter_organizer = filter_2d_organizer.Filter2DOrganizer() + self.density_array_list = None + if image_2d is not None: + self.original_image_list = image_2d.copy() + self.image_filtered = image_2d.copy() + self.image_backup = image_2d.copy() + if step_size is None: + self.step_size = [1.0, 1.0] + else: + self.step_size = step_size + elif image_1d is not None: + self.generate_images_from_1d_signal(image_1d, number_of_bins=number_of_bins) + else: + raise NotImplementedError + + @property + def images(self): + return self.image_filtered + + @property + def images_original(self): + return self.original_image_list + + def update_original_image(self, image_1d): + self.generate_images_from_1d_signal(image_1d) + + def apply_filters(self, force_execute=False): + if self.hash_filter_organizer(self.filter_organizer) != self.filter_organizer_hash or force_execute: + self.image_filtered = copy.deepcopy(self.original_image_list) + if len(self.filter_organizer.filters) > 0: + if not config.config["program_settings"]['filter_2d_multiprocessing']: + self.__apply_filters_without_multiprocessing() + else: + self.__apply_filters_with_multiprocessing() + self.filter_organizer_hash = self.hash_filter_organizer(self.filter_organizer) + return True + + return False + + def __apply_filters_with_multiprocessing(self): + pool = multiprocessing.Pool(config.config["program_settings"]["filter_2d_concurrency"]) + input_list = list() + for i in range(len(self.image_filtered)): + input_list.append(self.image_filtered[i]) + return_list = pool.map(self.__apply_filters_to_image, input_list) + for i in range(len(self.image_filtered)): + self.image_filtered[i] = return_list[i] + + def __apply_filters_without_multiprocessing(self): + for i in range(len(self.image_filtered)): + self.image_filtered[i] = self.__apply_filters_to_image( + self.image_filtered[i]) + + def __apply_filters_to_image(self, image): + image_nan_mask = np.isnan(image) + image = self.filter_organizer.get_filtered_image(image) + image[image_nan_mask] = np.nan + return image + + def generate_images_from_1d_signal(self, images_1d, number_of_bins=None): + self.__images_to_2d_grid(images_1d=images_1d, number_of_bins=number_of_bins) + + def _generate_ranges(self): + try: + x_range = [self.scan_image_collection.data_range["x_min"].min(), + self.scan_image_collection.data_range["x_max"].max()] + except AttributeError: + x_range = [self.scan_image_collection.data_range["x_min"], self.scan_image_collection.data_range["x_max"]] + try: + y_range = [self.scan_image_collection.data_range["y_min"].min(), + self.scan_image_collection.data_range["y_max"].max()] + except AttributeError: + y_range = [self.scan_image_collection.data_range["y_min"], self.scan_image_collection.data_range["y_max"]] + return x_range, y_range + + @staticmethod + def _generate_abs_ranges(x_range, y_range): + y_range_abs = abs(y_range[1] - y_range[0]) + x_range_abs = abs(x_range[1] - x_range[0]) + return x_range_abs, y_range_abs + + @staticmethod + def apply_compensation_value(x_range_abs, y_range_abs, x_range, y_range): + """Since all scan images should have consistent dimensions in both directions a compensation value is added""" + compensation_value = abs(x_range_abs - y_range_abs) / 2 + if x_range_abs > y_range_abs: + y_range[0] -= compensation_value + y_range[1] += compensation_value + else: + x_range[0] -= compensation_value + x_range[1] += compensation_value + return x_range, y_range + + @staticmethod + def calculate_step_size(x_range, y_range, number_of_bins): + step_size = [(x_range[1] - x_range[0]) / number_of_bins, (y_range[1] - y_range[0]) / number_of_bins] + return step_size + + def __images_to_2d_grid(self, images_1d, number_of_bins=None): + self.original_image_list = list() + self.density_array_list = list() + x_range, y_range = self._generate_ranges() + x_range_abs, y_range_abs = self._generate_abs_ranges(x_range, y_range) + x_range, y_range = self.apply_compensation_value(x_range_abs, y_range_abs, x_range, y_range) + if number_of_bins is None: + number_of_bins = self.scan.number_of_bins + self.step_size = self.calculate_step_size(x_range, y_range, number_of_bins) + self.x_range, self.y_range = x_range, y_range + use_multiple_processes = True + if "interpolation" in self.to_grid_method: + interpolation_dict = {"interpolation_cubic": 'cubic', + "interpolation_linear": 'linear', + "interpolation_nearest": 'nearest', } + grid_method = interpolation_dict.get(self.scan.grid_2d_method, "linear") + if use_multiple_processes is False: + for i in range(len(images_1d)): + new_image = self.image_interpolation_to_2d_grid(images_1d[i], + x_range, y_range, grid_method=grid_method) + if new_image is not None: + if self.scan.grid_2d_window == "sqaure": + new_image = get_square_inside_of_circle(new_image) + + self.original_image_list.append(new_image) + else: + manager = multiprocessing.Manager() + return_list = manager.list(range(len(images_1d))) + number_of_cores_to_use = multiprocessing.cpu_count() - 2 + if number_of_cores_to_use < 1: + number_of_cores_to_use = 1 + process_pool = multiprocessing.Pool(processes=number_of_cores_to_use) + grid_window = self.scan.grid_2d_window + for i in range(len(images_1d)): + process_pool.apply_async(self.interpolation_worker, + args=(i, images_1d[i], x_range, y_range, self.step_size, grid_method, + return_list, grid_window)) + process_pool.close() + process_pool.join() + self.original_image_list = list(return_list) + + elif self.to_grid_method == "histogram": + for i in range(len(images_1d)): + new_image, density = self.image_to_2d_histogram(images_1d[i], number_of_bins, + x_range, y_range, self.scan.grid_2d_window) + if new_image is not None: + self.original_image_list.append(new_image) + self.density_array_list.append(density) + + elif self.to_grid_method == "histogram_inpaint": + for i in range(len(images_1d)): + new_image, density = self.image_to_2d_histogram(images_1d[i], number_of_bins, + x_range, y_range, self.scan.grid_2d_window, + use_inpaint=True) + if new_image is not None: + self.original_image_list.append(new_image) + self.density_array_list.append(density) + self.image_filtered = self.original_image_list.copy() + self.image_backup = self.original_image_list.copy() + + @staticmethod + def image_to_2d_histogram(image, number_of_bins, x_range, y_range, grid_2d_method, use_inpaint=False): + if image.size == 0: + return None + try: + if config.config['import_h5']["use_4ths_column"]: + x, y, _, z = np.hsplit(image, 4) + else: + x, y, z, _ = np.hsplit(image, 4) + + except ValueError: + x, y, z = np.hsplit(image, 3) + + x, y, z = x.flatten(), y.flatten(), z.flatten() + histogram_array, x_edges, y_edges = np.histogram2d(x=x, y=y, range=[x_range, y_range], bins=number_of_bins, + weights=z, + density=False) + density, x_edges, y_edges = np.histogram2d(x=x, y=y, range=[x_range, y_range], + bins=number_of_bins) + np.seterr(divide='ignore', invalid='ignore') + histogram_array_scaled = histogram_array / density + np.seterr(divide='ignore', invalid='warn') + if use_inpaint is True: + histogram_array_scaled = fix_image_with_inpaint(histogram_array_scaled) + if grid_2d_method == "square": + array = get_square_inside_of_circle(histogram_array_scaled) + else: + array = histogram_array_scaled + return array, density + + def get_saasmi(self, image_number): + try: + saasmi_instance = self.saasmi_dict[image_number] + except KeyError: + saasmi_instance = self.saasmi_dict[image_number] = saasmi.SAASMI() + return saasmi_instance + + @staticmethod + def hash_filter_organizer(filter_organizer): + if filter_organizer is None: + return str([]) + return str(filter_organizer.filters) + + def saasmi_rag_remove_edge(self, image_number, key): + saasmi = self.saasmi_dict[image_number] + saasmi.remove_edge(key) + + def saasmi_rag_remove_all_edges_from_node(self, image_number, node_key): + saasmi = self.saasmi_dict[image_number] + saasmi.remove_all_edges_from_node(node_key) + + def saasmi_rag_add_edge(self, image_number, key): + saasmi = self.saasmi_dict[image_number] + saasmi.add_edge(key) + + def image_interpolation_to_2d_grid(self, image_1d, x_range, y_range, grid_method): + try: + if config.config['import_h5']["use_4ths_column"]: + x, y, _, z = image_1d.T + else: + x, y, z, _ = image_1d.T + except ValueError: + x, y, z = image_1d.T + xi = np.arange(x_range[0], x_range[1], self.step_size[0]) + yi = np.arange(y_range[0], y_range[1], self.step_size[1]) + xi, yi = np.meshgrid(xi, yi) + interpolated_image = interpolate.griddata((x, y), z, (xi, yi), + method=grid_method) + return interpolated_image + + @staticmethod + def interpolation_worker(index, image, x_range, y_range, step_size, grid_method, return_list, grid_window): + try: + if config.config['import_h5']["use_4ths_column"]: + x, y, _, z = image.T + else: + x, y, z, _ = image.T + except ValueError: + x, y, z = image.T + x, y, z = x.flatten(), y.flatten(), z.flatten() + xi = np.arange(x_range[0], x_range[1], step_size[0]) + yi = np.arange(y_range[0], y_range[1], step_size[1]) + yi, xi = np.meshgrid(yi, xi) + interpolated_image = interpolate.griddata((x, y), z, (xi, yi), + method=grid_method) + if grid_window == "square": + interpolated_image = get_square_inside_of_circle(interpolated_image) + return_list[index] = interpolated_image + return interpolated_image + + def saasmi_rag_remove_node(self, image_number, node_id): + saasmi = self.saasmi_dict[image_number] + node_information = saasmi.rag.nodes[node_id] + saasmi.remove_node(node_id) + return node_information + + def saasmi_rag_add_node(self, image_number, saasmi_position): + saasmi = self.saasmi_dict[image_number] + saasmi.add_node(saasmi_position) + + def get_atom_network(self, image_number, atom_network_type): + if image_number in self.saasmi_dict: + saasmi = self.saasmi_dict[image_number] + atom_network = saasmi.get_atom_network(atom_network_type) + return atom_network + + def recreate_atom_network(self, image_number, atom_network_type): + if image_number in self.saasmi_dict: + saasmi = self.saasmi_dict[image_number] + atom_network = saasmi.generate_atom_network(atom_network_type) + return atom_network diff --git a/model/distortion_project/__init__.py b/model/distortion_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/distortion_project/align_image_ransac.py b/model/distortion_project/align_image_ransac.py new file mode 100644 index 0000000..4dfca7b --- /dev/null +++ b/model/distortion_project/align_image_ransac.py @@ -0,0 +1,292 @@ +#!/usr/bin/python + +import math +import os +import os.path as osp + +import cv2 +import numpy as np +from numpy import linalg + + +def filter_matches(matches, ratio=0.75): + filtered_matches = [] + for m in matches: + if m[0].distance < m[1].distance * ratio: + filtered_matches.append(m[0]) + return filtered_matches + + +def image_distance(matches): + sumDistance = 0.0 + + for match in matches: + sumDistance += match.distance + + return sumDistance + + +def find_dimensions(image, homography): + base_p1 = np.ones(3, np.float32) + base_p2 = np.ones(3, np.float32) + base_p3 = np.ones(3, np.float32) + base_p4 = np.ones(3, np.float32) + + (y, x) = image.shape[:2] + + base_p1[:2] = [0, 0] + base_p2[:2] = [x, 0] + base_p3[:2] = [0, y] + base_p4[:2] = [x, y] + + max_x = None + max_y = None + min_x = None + min_y = None + + for pt in [base_p1, base_p2, base_p3, base_p4]: + + hp = np.matrix(homography, np.float32) * np.matrix(pt, np.float32).T + + hp_arr = np.array(hp, np.float32) + + normal_pt = np.array([hp_arr[0] / hp_arr[2], hp_arr[1] / hp_arr[2]], np.float32) + + if max_x is None or normal_pt[0, 0] > max_x: + max_x = normal_pt[0, 0] + + if max_y is None or normal_pt[1, 0] > max_y: + max_y = normal_pt[1, 0] + + if min_x is None or normal_pt[0, 0] < min_x: + min_x = normal_pt[0, 0] + + if min_y is None or normal_pt[1, 0] < min_y: + min_y = normal_pt[1, 0] + + min_x = min(0, min_x) + min_y = min(0, min_y) + + return min_x, min_y, max_x, max_y + + +def find_keypoints(img1, img2, mask1=None, mask2=None, detectorstr='ORB', thresh=500, debug=0): + if detectorstr in ['SIFT', 'ORB', 'SURF']: + # Define detector + detector = eval('cv2.xfeatures2d.{}_create()'.format(detectorstr)) + # if detectorstr == 'SURF': + # detector.hessianThreshold = thresh + else: + if debug == 1: print('Detector method %s is not implemented. Use SIFT.' % detectorstr) + detector = cv2.SIFT() + + # Find key points in base image for motion estimation + kp1i, des1 = detector.detectAndCompute(img1, mask1) + kp2i, des2 = detector.detectAndCompute(img2, mask2) + + return kp1i, des1, kp2i, des2 + + +def match_keypoints(kp1i, des1, kp2i, des2, matcherstr='Flann', debug=0): + # Parameters for nearest-neighbor matching + FLANN_INDEX_KDTREE = 1 # bug: flann enums are missing + flann_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) + + if matcherstr not in ['Flann', 'BF']: + if debug == 1: print('Matcher method %s is not implemented. Use Flann.' % matcherstr) + matcherstr = 'Flann' + + if matcherstr in ['Flann', 'BF']: + if matcherstr == 'Flann': + matcher = cv2.FlannBasedMatcher(flann_params, dict(checks=500)) + matches = matcher.knnMatch(np.asarray(des2, np.float32), trainDescriptors=np.asarray(des1, np.float32), k=2) + + if debug == 1: print("\t Match Count: ", len(matches)) + matches_subset = filter_matches(matches) + elif matcherstr == 'BF': + # create BFMatcher object + bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) + + matches = bf.match(des1, des2) + # Sort them in the order of their distance. + matches = sorted(matches, key=lambda x: x.distance) + matches_subset = matches + + return matches_subset + + +def matched_keypoints(kp1i, kp2i, matches_subset): + kp1 = [] + kp2 = [] + + for match in matches_subset: + kp1.append(kp1i[match.trainIdx]) + kp2.append(kp2i[match.queryIdx]) + return kp1, kp2 + + +def find_translation(kp1, kp2): + p1 = getrealpoints(kp1) + p2 = getrealpoints(kp2) + + H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 3.0) + idx = np.where(status.T[0])[0] + + poff = p1[idx] - p2[idx] + return poff.mean(axis=0) + + +def find_translation_offset(file1, file2, mask1=None, mask2=None, detectorstr='SIFT', matcherstr='Flann'): + img1rgb = cv2.imread(file1) + img2rgb = cv2.imread(file2) + + img1 = cv2.GaussianBlur(cv2.cvtColor(img1rgb, cv2.COLOR_BGR2GRAY), (5, 5), 0) + img2 = cv2.GaussianBlur(cv2.cvtColor(img2rgb, cv2.COLOR_BGR2GRAY), (5, 5), 0) + + # find key points + kp1i, des1, kp2i, des2 = find_keypoints(img1, img2, mask1, mask2, detectorstr) + # match key points + matches_subset = match_keypoints(kp1i, des1, kp2i, des2, matcherstr) + + kp1, kp2 = matched_keypoints(kp1i, kp2i, matches_subset) + + return find_translation(kp1, kp2) + + +def getrealpoints(kps): + return np.array([k.pt for k in kps]) + + +def findoffset(file1, file2, mask1=None, mask2=None, index=-1, ratioThres=0.1, detectorstr='SIFT', + matcherstr='Flann', only_translate=True): + img1rgb = cv2.imread(file1) + img2rgb = cv2.imread(file2) + + img1 = cv2.GaussianBlur(cv2.cvtColor(img1rgb, cv2.COLOR_BGR2GRAY), (5, 5), 0) + img2 = cv2.GaussianBlur(cv2.cvtColor(img2rgb, cv2.COLOR_BGR2GRAY), (5, 5), 0) + + # find key points + kp1i, des1, kp2i, des2 = find_keypoints(img1, img2, mask1, mask2, detectorstr) + # match key points + matches_subset = match_keypoints(kp1i, des1, kp2i, des2, matcherstr) + + print("\t Filtered Match Count: ", len(matches_subset)) + + distance = image_distance(matches_subset) + print("\t Distance from Key Image: ", distance) + + averagePointDistance = distance / float(len(matches_subset)) + print("\t Average Distance: ", averagePointDistance) + + kp1, kp2 = matched_keypoints(kp1i, kp2i, matches_subset) + + p1 = getrealpoints(kp1) + p2 = getrealpoints(kp2) + + H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 3.0) + print('%d / %d inliers/matched' % (np.sum(status), len(status))) + + # show the points and a line between the points on both images + + inlierRatio = float(np.sum(status)) / float(len(status)) + + H = H / H[2, 2] + H_inv = linalg.inv(H) + + if only_translate: + H[(0, 1, 2, 2), (1, 0, 0, 1)] = 0 + H[(0, 1), (0, 1)] = 1 + H_inv[(0, 1, 2, 2), (1, 0, 0, 1)] = 0 + H_inv[(0, 1), (0, 1)] = 1 + + if inlierRatio > ratioThres: + + (min_x, min_y, max_x, max_y) = find_dimensions(img2, H_inv) + + # Adjust max_x and max_y by base img size + max_x = max(max_x, img1.shape[1]) + max_y = max(max_y, img1.shape[0]) + + move_h = np.matrix(np.identity(3), np.float32) + + if (min_x < 0): + move_h[0, 2] += -min_x + max_x += -min_x + + if (min_y < 0): + move_h[1, 2] += -min_y + max_y += -min_y + + print("Homography: \n", H) + print("Inverse Homography: \n", H_inv) + print("Min Points: ", (min_x, min_y)) + + mod_inv_h = move_h * H_inv + + print("move_h: ", move_h) + print("mod_inv_h: ", mod_inv_h) + + img_w = int(math.ceil(max_x)) + img_h = int(math.ceil(max_y)) + + print("New Dimensions: ", (img_w, img_h)) + + # Warp the new image given the homography from the old image + img1_warp = cv2.warpPerspective(img1rgb, move_h, (img_w, img_h)) + print("Warped base image") + + img2_warp = cv2.warpPerspective(img2rgb, mod_inv_h, (img_w, img_h)) + print("Warped next image") + + # Put the base image on an enlarged palette + enlarged_base_img = np.zeros((img_h, img_w, 3), np.uint8) + + print("Enlarged Image Shape: ", enlarged_base_img.shape) + print("Base Image Shape: ", img1.shape) + print("Base Image Warp Shape: ", img1_warp.shape) + + # Create a mask from the warped image for constructing masked composite + ret, data_map = cv2.threshold(cv2.cvtColor(img2_warp, cv2.COLOR_BGR2GRAY), 0, 255, cv2.THRESH_BINARY) + + enlarged_base_img = cv2.add(enlarged_base_img, img1_warp, mask=np.bitwise_not(data_map), dtype=cv2.CV_8U) + + # Now add the warped image + final_img = cv2.add(enlarged_base_img, img2_warp, dtype=cv2.CV_8U) + + # Crop off the black edges + final_gray = cv2.cvtColor(final_img, cv2.COLOR_BGR2GRAY) + _, thresh = cv2.threshold(final_gray, 1, 255, cv2.THRESH_BINARY) + contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) + print("Found %d contours..." % (len(contours))) + + # max_area = 0 + # best_rect = (0, 0, 0, 0) + # + # for cnt in contours: + # x, y, w, h = cv2.boundingRect(cnt) + # # print("Bounding Rectangle: ", (x,y,w,h)) + # + # deltaHeight = h - y + # deltaWidth = w - x + # + # area = deltaHeight * deltaWidth + # + # if area > max_area and deltaHeight > 0 and deltaWidth > 0: + # max_area = area + # best_rect = (x, y, w, h) + + if 0: + # Write out the current round + path, basename = osp.split(file1) + ext = osp.splitext(basename)[1] + newpath = osp.join(path, 'stitched') + if not osp.exists(newpath): + os.makedirs(newpath) + final_filename = osp.join(newpath, basename.replace(ext, '_{}-{}'.format(index, index + 1) + ext)) + cv2.imwrite(final_filename, final_img) + + return final_img + + else: + raise ValueError('Error during stitching!!!') + diff --git a/model/distortion_project/distortion_drift_correction.py b/model/distortion_project/distortion_drift_correction.py new file mode 100644 index 0000000..7a8478b --- /dev/null +++ b/model/distortion_project/distortion_drift_correction.py @@ -0,0 +1,80 @@ +import cv2 +import numpy as np +import scipy.ndimage +import skimage +import skimage.transform + +from model.distortion_project import align_image_ransac + + +def gen_warp_transform(image1, image2, progress=0, debug=0, tftype='polynomial', islog=False): + img1 = skimage.img_as_ubyte(image1) + img2 = skimage.img_as_ubyte(image2) + + img1 = cv2.GaussianBlur(img1, (5, 5), 0) + img2 = cv2.GaussianBlur(img2, (5, 5), 0) + + if progress == 1: print('.', end='') + + # find key points + kp1i, des1, kp2i, des2 = align_image_ransac.find_keypoints(img1, img2, None, None, 'SURF', + thresh=50, debug=debug) + if progress == 1: print('.', end='') + # match key points + matches_subset = align_image_ransac.match_keypoints(kp1i, des1, kp2i, des2, 'Flann', debug=debug) + if progress == 1: print('.', end='') + if debug == 1: print("\t Filtered Match Count: ", len(matches_subset)) + + distance = align_image_ransac.image_distance(matches_subset) + if progress == 1: print('.', end='') + if debug == 1: print("\t Distance from Key Image: ", distance) + + if len(matches_subset) == 0: + average_point_distance = 0 + else: + average_point_distance = distance / float(len(matches_subset)) + if progress == 1: print('.', end='') + if debug == 1: print("\t Average Distance: ", average_point_distance) + + kp1, kp2 = align_image_ransac.matched_keypoints(kp1i, kp2i, matches_subset) + + p1 = align_image_ransac.getrealpoints(kp1) + p2 = align_image_ransac.getrealpoints(kp2) + H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 10.0) + if progress == 1: print('.', end='') + if debug == 1: print('%d / %d inliers/matched' % (np.sum(status), len(status))) + + # show the points and a line between the points on both images + good_pts = np.where(status)[0] + inlinep1 = np.int_(p1[tuple(good_pts), :]) + inlinep2 = np.int_(p2[tuple(good_pts), :]) + + src = inlinep2 + dst = inlinep1 + + tform = skimage.transform.estimate_transform(tftype, src, dst) + + return tform + + +def get_channel_for_warp(image2, corrchannel, flipud=1, progress=0, debug=0): + if progress == 1: print('[', end='') + if progress == 1: print('.', end='') + + if flipud == 1: + channel = np.flipud(image2.data[corrchannel]) + else: + channel = image2.data[corrchannel] + + if progress == 1: print('.', end='') + if progress == 1: print(']') + return channel + + +def apply_warp_transform(channel, tform, flipud=1, progress=0, debug=0): + if progress == 1: print('[', end='') + res = skimage.transform.warp_coords(tform, channel.shape) + corrected_channel = scipy.ndimage.map_coordinates(channel, res) + if progress == 1: print('.', end='') + if progress == 1: print(']') + return corrected_channel diff --git a/model/distortion_project/tools.py b/model/distortion_project/tools.py new file mode 100644 index 0000000..6a3844e --- /dev/null +++ b/model/distortion_project/tools.py @@ -0,0 +1,60 @@ +import numpy as np + + +def line_fit(line, order=1, box=[0]): + """ + Do a nth order polynomial line flattening + + Parameters + ---------- + line : 1d array-like + order : integer + + Returns + ------- + result : 1d array-like + same shape as data + """ + if order < 0: + raise ValueError('expected deg >= 0') + newline = line + if len(box) == 2: + newline = line[box[0]:box[1]] + x = np.arange(len(newline)) + k = np.isfinite((newline)) + if not np.isfinite(newline).any(): + return line + coefficients = np.polyfit(x[k], newline[k], order) + + return line - np.polyval(coefficients, np.arange(len(line))) + + +def line_flatten_image(data, order=1, axis=0, box=[0]): + """ + Do a line flattening + + Parameters + ---------- + data : 2d array + order : integer + axis : integer + axis perpendicular to lines + + Returns + ------- + result : array-like + same shape as data + """ + + if axis == 1: + data = data.T + + ndata = np.zeros_like(data) + + for i, line in enumerate(data): + ndata[i, :] = line_fit(line, order, box) + + if axis == 1: + ndata = ndata.T + + return ndata diff --git a/model/drift_detection/__init__.py b/model/drift_detection/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/model/drift_detection/__init__.py @@ -0,0 +1 @@ + diff --git a/model/drift_detection/cross_correlation_drift_detection.py b/model/drift_detection/cross_correlation_drift_detection.py new file mode 100644 index 0000000..b59b8b0 --- /dev/null +++ b/model/drift_detection/cross_correlation_drift_detection.py @@ -0,0 +1,115 @@ +import numpy as np +import scipy.signal +import skimage.registration + +from model.data import scan_organizier + + +class CrossCorrelationDriftDetection: + def get_drift_by_image(self, source_image, target_image): + correlation_map = scipy.signal.fftconvolve(source_image, target_image, mode='same') + result = np.where(correlation_map == np.amax(correlation_map)) + list_of_coordinates = list(zip(result[0], result[1])) + drift = list_of_coordinates[0] + return drift + + def get_drift_by_image_number(self, source_image_number, target_image_number): + source_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[source_image_number].copy()) + target_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[target_image_number].copy()) + return self.get_drift_by_image(source_image, target_image) + + def get_drift_vector(self, image_list): + pass + + +class PhaseCrossCorrelationDriftDetection: + def get_drift_by_image(self, source_image, target_image): + correlation_map = scipy.signal.fftconvolve(source_image, target_image, mode='same') + result = np.where(correlation_map == np.amax(correlation_map)) + list_of_coordinates = list(zip(result[0], result[1])) + drift = list_of_coordinates[0] + return drift + + def get_drift_by_image_number(self, source_image_number, target_image_number): + source_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[source_image_number].copy()) + target_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[target_image_number].copy()) + return self.get_drift_by_image(source_image, target_image) + + def get_most_occurent_drift_vector(self, drift_vector_array): + # Stolen from https://stackoverflow.com/questions/27000092/count-how-many-times-each-row-is-present-in-numpy-array + dt = np.dtype((np.void, drift_vector_array.dtype.itemsize * drift_vector_array.shape[1])) + b = np.ascontiguousarray(drift_vector_array).view(dt) + unq, cnt = np.unique(b, return_counts=True) + unq = unq.view(drift_vector_array.dtype).reshape(-1, drift_vector_array.shape[1]) + return unq, cnt + + def get_drift_vector_array(self, drift_vector_list, image_list_length): + drift_vector_array = np.empty(shape=(image_list_length, 2)) + for drift_vector_index in range(len(drift_vector_list)): + if drift_vector_index == 0: + drift_vector_array[drift_vector_index] = drift_vector_list[drift_vector_index] + else: + drift_vector = drift_vector_list[drift_vector_index] + drift_vector_array[drift_vector_index] = drift_vector_array[drift_vector_index - 1] + drift_vector + drift_vector_array[len(drift_vector_list):] = drift_vector_array[len(drift_vector_list) - 1] + return drift_vector_array + + def get_constant_drift_vector_array(self, drift_vector_list, image_list_length): + unique_drift_vectors, count = self.get_most_occurent_drift_vector(drift_vector_array=drift_vector_list) + min_index = np.argmax(count) + drift_vector = unique_drift_vectors[min_index] + drift_vector_array_x = np.arange(image_list_length) * drift_vector[0] + drift_vector_array_y = np.arange(image_list_length) * drift_vector[1] + drift_vector_array = np.vstack((drift_vector_array_x, drift_vector_array_y)) + return drift_vector_array.T + + def clean_drift_vector_list(self, drift_vector_list, maximum_vector_length): + for vector_index, vector in enumerate(drift_vector_list): + if np.sqrt(vector.dot(vector)) > maximum_vector_length > 0 and vector_index > 0: + drift_vector_list[vector_index] = drift_vector_list[vector_index - 1] + + return drift_vector_list + + def get_average_drift_vector_list(self, image_offset_drift_vector_list): + drift_vector_array = None + for drift_vector_list in image_offset_drift_vector_list: + if drift_vector_array is None: + drift_vector_array = drift_vector_list + else: + drift_vector_array += drift_vector_list + drift_vector_array /= len(image_offset_drift_vector_list) + return drift_vector_array + + def generate_drift_vector_list(self, image_list, compare_image_offset): + drift_vector_list = np.empty((len(image_list) - compare_image_offset, 2)) + for i in range(len(image_list) - compare_image_offset): + image_1 = image_list[i] + image_2 = image_list[i + compare_image_offset] + + reference_mask = ~np.isnan(image_1) + moving_mask = ~np.isnan(image_2) + drift_vector = skimage.registration.phase_cross_correlation(image_1, image_2, + reference_mask=reference_mask, + moving_mask=moving_mask) + drift_vector = drift_vector / compare_image_offset + drift_vector_list[i] = drift_vector + return drift_vector_list + + def get_drift_vector(self, image_list, parameters): + compare_image_offset_list = parameters['image_offset_list'] + maximum_vector_length = parameters['maximum_vector_length'] + method = parameters["method"] + image_offset_drift_vector_list = [] + for compare_image_offset in compare_image_offset_list: + drift_vector_list = self.generate_drift_vector_list(image_list, compare_image_offset) + if maximum_vector_length > 0: + drift_vector_list = self.clean_drift_vector_list(drift_vector_list, + maximum_vector_length=maximum_vector_length) + if method == "constant drift": + drift_vector_array = self.get_constant_drift_vector_array(drift_vector_list, len(image_list)) + else: + drift_vector_array = self.get_drift_vector_array(drift_vector_list=drift_vector_list, + image_list_length=len(image_list)) + image_offset_drift_vector_list.append(drift_vector_array) + drift_vector_array_average = self.get_average_drift_vector_list(image_offset_drift_vector_list) + return drift_vector_array_average diff --git a/model/drift_detection/masked_register_translation_drift_detection.py b/model/drift_detection/masked_register_translation_drift_detection.py new file mode 100644 index 0000000..eac0154 --- /dev/null +++ b/model/drift_detection/masked_register_translation_drift_detection.py @@ -0,0 +1,20 @@ +import numpy as np +from skimage.feature import masked_register_translation + +from model.data import scan_organizier + + +class MaskedRegisterDriftDetection: + def get_drift_by_image(self, source_image, target_image): + masked_translation = masked_register_translation(source_image, target_image, src_mask=source_image, + target_mask=target_image) + drift = masked_translation + return drift + + def get_drift_by_image_number(self, source_image_number, target_image_number): + source_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[source_image_number].copy()) + target_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[target_image_number].copy()) + return self.get_drift_by_image(source_image, target_image) + + def get_drift_vector(self, image_list): + pass \ No newline at end of file diff --git a/model/drift_detection/register_translation_drift_detection.py b/model/drift_detection/register_translation_drift_detection.py new file mode 100644 index 0000000..d433aa1 --- /dev/null +++ b/model/drift_detection/register_translation_drift_detection.py @@ -0,0 +1,20 @@ +import numpy as np +from skimage.feature import register_translation + +from model.data import scan_organizier + + +class RegisterDriftDetection: + def get_drift_by_image(self, source_image, target_image): + masked_translation = register_translation(source_image, target_image) + drift = masked_translation + return drift + + def get_drift_by_image_number(self, source_image_number, target_image_number): + source_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[source_image_number].copy()) + target_image = np.nan_to_num(scan_organizier.ScanOrganizer.current_scan.images[target_image_number].copy()) + return self.get_drift_by_image(source_image, target_image) + + + def get_drift_vector(self): + pass \ No newline at end of file diff --git a/model/feature_detector/__init__.py b/model/feature_detector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/feature_detector/blob_feature_detector.py b/model/feature_detector/blob_feature_detector.py new file mode 100644 index 0000000..477799c --- /dev/null +++ b/model/feature_detector/blob_feature_detector.py @@ -0,0 +1,48 @@ +from math import sqrt + +import numpy as np +from skimage.feature import blob_log + +from model.data import scan_organizier + + +class BlobFeatureDetector: + def get_feature_list(self, image, parameters): + current_scan = scan_organizier.ScanOrganizer.current_scan + + min_sigma = parameters["min_sigma"] + max_sigma = parameters["max_sigma"] + num_sigma = parameters["num_sigma"] + threshold = parameters["threshold"] + detect_maxima = parameters["detect_maxima"] + overlap = parameters["overlap"] + image = image.copy() + image_min = np.nanmin(image) + image_max = np.nanmax(image) + + normalize_image = image - image_min + normalize_image = normalize_image / image_max + if not detect_maxima: + normalize_image = np.nanmax(normalize_image) - normalize_image + blobs_log = blob_log(normalize_image, min_sigma=min_sigma, max_sigma=max_sigma, + num_sigma=num_sigma, + threshold=threshold, overlap=overlap) + h, w = image.shape + blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2) + # swapping first and second column, because they are in wrong order + blobs_log[:, [0, 1]] = blobs_log[:, [1, 0]] + blobs_log_list = [[float(blob[0]), float(blob[1]), float(blob[2])] for blob in + blobs_log] + return blobs_log_list + + @classmethod + def get_inputs(cls): + return {"Minimum Sigma": "double", + "Maximum Sigma": "double", + "Num Sigma": "int", + "Threshold": "double", + "Overlap": "double"} + + @classmethod + def __str__(cls): + return "Laplacian of Gaussian Blob Detector" diff --git a/model/feature_detector/detector_factory.py b/model/feature_detector/detector_factory.py new file mode 100644 index 0000000..80147f4 --- /dev/null +++ b/model/feature_detector/detector_factory.py @@ -0,0 +1,21 @@ +import logging + +from model import config +from model.feature_detector import blob_feature_detector + +log = logging.getLogger(__name__) + +available_detectors = [blob_feature_detector.BlobFeatureDetector] + + +def get_feature_detector(feature_detector_name): + if feature_detector_name is None: + feature_detector_name = config.get_value("feature_settings", "feature_detector") + for detector in available_detectors: + if str(detector) == feature_detector_name: + return detector() + log.error("No detector found with name {}, return default detector".format(feature_detector_name)) + return blob_feature_detector.BlobFeatureDetector() + +def get_detectors_str_list(): + return [str(detector) for detector in available_detectors] diff --git a/model/feature_detector/orb_feature_detection.py b/model/feature_detector/orb_feature_detection.py new file mode 100644 index 0000000..dce94ea --- /dev/null +++ b/model/feature_detector/orb_feature_detection.py @@ -0,0 +1,34 @@ +from math import sqrt +from matplotlib import pyplot as plt +import cv2 +import numpy as np +from skimage.feature import ORB + +from model.data import scan_organizier + + +class ORBFeatureDetector: + def get_feature_list(self, image): + image = image.copy() + image_min = np.nanmin(image) + normalised_image = image - image_min + image = np.nan_to_num(normalised_image) + image_max = np.nanmax(image) + image = np.true_divide(image, image_max) + image *= 255 + image = image.astype(np.uint8) + descriptor_extractor = ORB(n_keypoints=100, n_scales=20) + + descriptor_extractor.detect_and_extract(image) + current_scan = scan_organizier.ScanOrganizer.current_scan + step_size_x, step_size_y = current_scan.step_size + keypoints1 = descriptor_extractor.keypoints + descriptors1 = descriptor_extractor.descriptors + keypoints1[:, [0, 1]] = keypoints1[:, [1, 0]] + keypoints1_list = [[float(blob[0] * step_size_x), float(blob[1] * step_size_y), 0] for blob in + keypoints1] + return keypoints1_list + + @classmethod + def __str__(cls): + return "ORB Detector" \ No newline at end of file diff --git a/model/feature_detector/simple_blob_detector.py b/model/feature_detector/simple_blob_detector.py new file mode 100644 index 0000000..d8576f8 --- /dev/null +++ b/model/feature_detector/simple_blob_detector.py @@ -0,0 +1,59 @@ +# https://stackoverflow.com/questions/8076889/how-to-use-opencv-simpleblobdetector +import cv2 +import numpy as np + +from model.data import scan_organizier + + +class SimpleBlobFeatureDetector: + + def get_feature_list(self, image): + image = image.copy() + image_min = np.nanmin(image) + normalised_image = image - image_min + image = np.nan_to_num(normalised_image) + image_max = np.nanmax(image) + image = np.true_divide(image, image_max) + image *= 255 + image = image.astype(np.uint8) + + params = cv2.SimpleBlobDetector_Params() + + # Change thresholds + # params.minThreshold = 2 + # params.maxThreshold = 1500 + + # Filter by Area. + # params.filterByArea = True + # params.minArea = 1500 + + # Filter by Circularity + # params.filterByCircularity = True + # params.minCircularity = 0.1 + + # Filter by Convexity + # params.filterByConvexity = True + # params.minConvexity = 0.87 + + # Filter by Inertia + # params.filterByInertia = True + # params.minInertiaRatio = 0.01 + + # Create a detector with the parameters + detector = cv2.SimpleBlobDetector_create() + # Detect blobs. + keypoints1 = detector.detect(image) + + # Draw detected blobs as red circles. + # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures + # the size of the circle corresponds to the size of blob + current_scan = scan_organizier.ScanOrganizer.current_scan + step_size_x, step_size_y = current_scan.step_size + keypoints1[:, [0, 1]] = keypoints1[:, [1, 0]] + keypoints1_list = [[float(blob[0] * step_size_x), float(blob[1] * step_size_y), 0] for blob in + keypoints1] + return keypoints1_list + + @classmethod + def __str__(cls): + return "Simple Blob" diff --git a/model/filter/__init__.py b/model/filter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/filter/filter_1d/__init__.py b/model/filter/filter_1d/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/filter/filter_1d/brickwall_bandpass.py b/model/filter/filter_1d/brickwall_bandpass.py new file mode 100644 index 0000000..b6bdce8 --- /dev/null +++ b/model/filter/filter_1d/brickwall_bandpass.py @@ -0,0 +1,59 @@ +import re + +import numpy as np + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "1D Brickwall Bandpass" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(min: ({float_regex}), max: ({float_regex})\)$".format(class_name=CLASS_NAME_STR, + float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class BrickwallBandPass1D(metaclass=MC): + def __init__(self, min_frequency=3000, max_frequency=20000): + self.min_frequency = min_frequency + self.max_frequency = max_frequency + + def get_filtered_image(self, image): + z = image[:, 2] + z_fft = np.fft.fft(z) + z_fft_shifted = np.fft.fftshift(z_fft) + mask = self.get_mask(len(z_fft_shifted)) + z_fft_masked = z_fft_shifted * mask + z_bandpassed = np.fft.ifft(np.fft.ifftshift(z_fft_masked)) + image[:, 2] = z_bandpassed.real + return image + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + def get_mask(self, number_of_values): + center = int(number_of_values / 2) + mask_array = np.ones(number_of_values, dtype=bool) + if center - self.max_frequency <= 0: + mask_array[center - self.min_frequency:center + self.min_frequency] = False + else: + mask_array[center - self.min_frequency:center + self.min_frequency] = False + mask_array[0:center - self.max_frequency] = False + mask_array[center + self.max_frequency:] = False + if self.min_frequency == 0: + mask_array[center] = True + return mask_array + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimum Frequency": ["int", 100], + "Maximum Frequency": ["int", 20000]} diff --git a/model/filter/filter_1d/brickwall_bandstop.py b/model/filter/filter_1d/brickwall_bandstop.py new file mode 100644 index 0000000..fbbe828 --- /dev/null +++ b/model/filter/filter_1d/brickwall_bandstop.py @@ -0,0 +1,59 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "1D Brickwall Bandstop" +from model.filter.filter_regex_helper import float_regex + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(min: ({float_regex}), max: ({float_regex})\)$".format(class_name=CLASS_NAME_STR, + float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class BrickwallBandStop1D(metaclass=MC): + def __init__(self, min_frequency=3000, max_frequency=20000): + self.min_frequency = min_frequency + self.max_frequency = max_frequency + + def get_filtered_image(self, image): + z = image[:, 2] + z_fft = np.fft.fft(z) + z_fft_shifted = np.fft.fftshift(z_fft) + mask = self.get_mask(len(z_fft_shifted)) + z_fft_masked = z_fft_shifted * mask + z_bandpassed = np.fft.ifft(np.fft.ifftshift(z_fft_masked)) + image[:, 2] = z_bandpassed.real + return image + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + def get_mask(self, number_of_values): + center = int(number_of_values / 2) + mask_array = np.ones(number_of_values, dtype=bool) + if center - self.max_frequency <= 0: + mask_array[center - self.min_frequency:center + self.min_frequency] = False + else: + mask_array[center - self.min_frequency:center + self.min_frequency] = False + mask_array[0:center - self.max_frequency] = False + mask_array[center + self.max_frequency:] = False + mask_array = ~mask_array + if self.min_frequency == 0: + mask_array[center] = False + return mask_array + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimum Frequency": ["int", 100], + "Maximum Frequency": ["int", 20000]} diff --git a/model/filter/filter_1d/butterworth_bandpass.py b/model/filter/filter_1d/butterworth_bandpass.py new file mode 100644 index 0000000..7b47783 --- /dev/null +++ b/model/filter/filter_1d/butterworth_bandpass.py @@ -0,0 +1,57 @@ +import re + +import scipy.signal + +from model.data import scan_organizier +from model.filter.filter_regex_helper import float_regex, int_regex + +CLASS_NAME_STR = "1D Butterworth Bandpass" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(order: ({int_regex}), min: ({float_regex}), max: ({float_regex})\)$".format( + class_name=CLASS_NAME_STR, int_regex=int_regex, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class ButterworthBandpass1D(metaclass=MC): + def __init__(self, order=1, min_frequency=3000, max_frequency=20000): + self.frequency_sampling = scan_organizier.ScanOrganizer.current_scan.frequency_sampling + self.order = order + self.min_frequency = min_frequency + self.max_frequency = max_frequency + + def get_filtered_image(self, image): + z = image[:, 2] + input_min_frequency = self.min_frequency * 2 / image.shape[0] + input_max_frequency = self.max_frequency * 2 / image.shape[0] + if input_min_frequency <= 0 and input_max_frequency > 1: + return image + elif input_max_frequency > 1: + b, a = scipy.signal.butter(self.order, input_min_frequency, btype="highpass") + elif input_min_frequency <= 0: + b, a = scipy.signal.butter(self.order, input_max_frequency, btype="lowpass") + else: + b, a = scipy.signal.butter(self.order, [input_min_frequency, input_max_frequency], btype="bandpass") + z_butterworth = scipy.signal.filtfilt(b, a, z) + image[:, 2] = z_butterworth.real + return image + + def __repr__(self): + return "{class_name} (order: {order}, min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + order=self.order, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Order": ["int", 5], + "Minimum Frequency": ["int", 100], + "Maximum Frequency": ["int", 20000]} diff --git a/model/filter/filter_1d/butterworth_bandstop.py b/model/filter/filter_1d/butterworth_bandstop.py new file mode 100644 index 0000000..f208dda --- /dev/null +++ b/model/filter/filter_1d/butterworth_bandstop.py @@ -0,0 +1,55 @@ +import re + +import scipy.signal + +from model.data import scan_organizier +from model.filter.filter_regex_helper import int_regex, float_regex + +CLASS_NAME_STR = "1D Butterworth Bandstop" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(order: ({int_regex}), min: ({float_regex}), max: ({float_regex})\)$".format( + class_name=CLASS_NAME_STR, int_regex=int_regex, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class ButterworthBandstop1D(metaclass=MC): + def __init__(self, order=1, min_frequency=3000, max_frequency=20000): + self.frequency_sampling = scan_organizier.ScanOrganizer.current_scan.frequency_sampling + self.order = order + self.min_frequency = min_frequency + self.max_frequency = max_frequency + + def get_filtered_image(self, image): + z = image[:, 2] + input_min_frequency = self.min_frequency * 2 / image.shape[0] + input_max_frequency = self.max_frequency * 2 / image.shape[0] + if input_max_frequency > 1: + b, a = scipy.signal.butter(self.order, input_min_frequency, btype="lowpass") + elif input_min_frequency <= 0: + b, a = scipy.signal.butter(self.order, input_max_frequency, btype="highpass") + else: + b, a = scipy.signal.butter(self.order, [input_min_frequency, input_max_frequency], btype="bandstop") + z_butterworth = scipy.signal.filtfilt(b, a, z) + image[:, 2] = z_butterworth.real + return image + + def __repr__(self): + return "{class_name} (order: {order}, min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + order=self.order, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Order": ["int", 5], + "Minimum Frequency": ["int", 100], + "Maximum Frequency": ["int", 1000]} diff --git a/model/filter/filter_1d/correlation_with_x.py b/model/filter/filter_1d/correlation_with_x.py new file mode 100644 index 0000000..5ddc0e6 --- /dev/null +++ b/model/filter/filter_1d/correlation_with_x.py @@ -0,0 +1,36 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "X Correlation" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class CorrelationX(metaclass=MC): + def get_filtered_image(self, image): + image_filter = image.copy() + z = image_filter[:, 2] + z_fft = np.fft.fft(z) + z_fft_shifted = np.fft.fftshift(z_fft) + x = image_filter[:, 0] + x_fft = np.fft.fft(x) + x_fft_shifted = np.fft.fftshift(x_fft) + z_correlated = z_fft_shifted * x_fft_shifted + image_filter[:, 2] = np.fft.ifft(np.fft.ifftshift(z_correlated)).real + return image_filter + + def __repr__(cls): + return CLASS_NAME_STR + + @classmethod + def necessary_inputs(cls) -> dict: + return {} diff --git a/model/filter/filter_1d/correlation_with_y.py b/model/filter/filter_1d/correlation_with_y.py new file mode 100644 index 0000000..bfd0681 --- /dev/null +++ b/model/filter/filter_1d/correlation_with_y.py @@ -0,0 +1,36 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "Y Correlation" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class CorrelationY(metaclass=MC): + def get_filtered_image(self, image): + image_filter = image.copy() + z = image_filter[:, 2] + z_fft = np.fft.fft(z) + z_fft_shifted = np.fft.fftshift(z_fft) + y = image_filter[:, 1] + y_fft = np.fft.fft(y) + y_fft_shifted = np.fft.fftshift(y_fft) + z_correlated = z_fft_shifted * y_fft_shifted + image_filter[:, 2] = np.fft.ifft(np.fft.ifftshift(z_correlated)).real + return image_filter + + def __repr__(cls): + return CLASS_NAME_STR + + @classmethod + def necessary_inputs(cls) -> dict: + return {} diff --git a/model/filter/filter_1d/dc_frequency_remover.py b/model/filter/filter_1d/dc_frequency_remover.py new file mode 100644 index 0000000..9427aaf --- /dev/null +++ b/model/filter/filter_1d/dc_frequency_remover.py @@ -0,0 +1,31 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "DC Remover" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class DCRemover1D(metaclass=MC): + def get_filtered_image(self, image): + image_filter = image.copy() + z = image_filter[:, 2] + z = z - np.mean(z) + image_filter[:, 2] = z + return image_filter + + def __repr__(cls): + return CLASS_NAME_STR + + @classmethod + def necessary_inputs(cls) -> dict: + return {} diff --git a/model/filter/filter_1d/frequency_manipulator.py b/model/filter/filter_1d/frequency_manipulator.py new file mode 100644 index 0000000..e67ac76 --- /dev/null +++ b/model/filter/filter_1d/frequency_manipulator.py @@ -0,0 +1,45 @@ +import re + +import numpy as np + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "Frequency Manipulator" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Frequency: ({float_regex}), Value: ({float_regex})\)$".format(class_name=CLASS_NAME_STR, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class FrequencyManipulator(metaclass=MC): + def __init__(self, frequency= 0, frequency_value=0.0): + self.frequency = frequency + self.frequency_value = frequency_value + + def get_filtered_image(self, image): + image_filter = image.copy() + z = image_filter[:, 2] + z_fft = np.fft.fft(z) + z_fft[self.frequency] = self.frequency_value + z_fft[-self.frequency] = self.frequency_value + image_filter[:, 2] = np.fft.ifft((z_fft)).real + return image_filter + + def __repr__(self): + return "{class_name} (Frequency: {frequency}, Value: {frequency_value})".format( + class_name=CLASS_NAME_STR, + frequency=self.frequency, + frequency_value=self.frequency_value) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Frequency": ["int", 0], + "Value": ["double", 0.0], + } diff --git a/model/filter/filter_1d/frequency_remover.py b/model/filter/filter_1d/frequency_remover.py new file mode 100644 index 0000000..0aa8b96 --- /dev/null +++ b/model/filter/filter_1d/frequency_remover.py @@ -0,0 +1,54 @@ +import re + +import numpy as np + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "Frequency Remover" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Sample Frequency: {float_regex} , Frequencies: \[({float_regex}(?:, {float_regex})*)\]\)$".format( + class_name=CLASS_NAME_STR, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + values = re.match(cls.FILTER_REGEX, class_string).groups() + class_name, input_list_str = values + input_list = input_list_str.split(",") + input_list = [input.strip() for input in input_list] + return (class_name, input_list) + + +class FrequencyRemover(metaclass=MC): + def __init__(self, sample_frequency=None, frequencies=None): + if frequencies is None: + frequencies = [0] + self.sample_frequency = sample_frequency + self.frequencies = frequencies + + def get_filtered_image(self, image): + image_filter = image.copy() + sample_frequency_delta = round(self.sample_frequency / image_filter.shape[0], 3) + z = image_filter[:, 2] + z_fft = np.fft.fft(z) + for frequency in self.frequencies: + frequency_corrected = int(round(frequency / sample_frequency_delta)) + z_fft[frequency_corrected] = 0 + z_fft[-frequency_corrected] = 0 + image_filter[:, 2] = np.fft.ifft((z_fft)).real + return image_filter + + def __repr__(self): + return "{class_name} (Sample Frequency: {sample_frequency}, Frequencies: {frequencies})".format( + class_name=CLASS_NAME_STR, + sample_frequency=self.sample_frequency, + frequencies=self.frequencies) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Sample Frequency": ["label", "sample_frequency"], + "Frequencies": ["selected_point_list", 0]} diff --git a/model/filter/filter_1d/gaussian.py b/model/filter/filter_1d/gaussian.py new file mode 100644 index 0000000..401a8dd --- /dev/null +++ b/model/filter/filter_1d/gaussian.py @@ -0,0 +1,42 @@ +import re + +import scipy.ndimage + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "1D Gaussian" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Sigma: {float_regex},Truncate: ({float_regex})\)$".format(class_name=CLASS_NAME_STR, + float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class Gauss1D(metaclass=MC): + def __init__(self, sigma=1.0, truncate=4.0): + self.sigma = sigma + self.truncate = truncate + + def get_filtered_image(self, image): + z = image[:, 2] + z_gaussed = scipy.ndimage.gaussian_filter1d(z, sigma=self.sigma, truncate=self.truncate) + image[:, 2] = z_gaussed + return image + + def __repr__(self): + return "{class_name} (Sigma: {sigma},Truncate: {truncate})".format( + class_name=CLASS_NAME_STR, + sigma=self.sigma, + truncate=self.truncate) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Sigma": ["double", 1], + "Truncate": ["double", 4]} diff --git a/model/filter/filter_1d/hann_smoothing.py b/model/filter/filter_1d/hann_smoothing.py new file mode 100644 index 0000000..3d5a856 --- /dev/null +++ b/model/filter/filter_1d/hann_smoothing.py @@ -0,0 +1,40 @@ +import re + +import scipy.signal +import scipy.signal + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "1D Hann Smoothing" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Window Size: ({float_regex})\)$".format(class_name=CLASS_NAME_STR, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class HannSmoothing1D(metaclass=MC): + def __init__(self, window_size=3000): + self.window_size = window_size + + def get_filtered_image(self, image): + z = image[:, 2] + han_filter = scipy.signal.windows.hann(self.window_size) + z_filtered = scipy.signal.convolve(z, han_filter, mode='same') / sum(han_filter) + image[:, 2] = z_filtered + return image + + def __repr__(self): + return "{class_name} (Window Size: {window_size})".format( + class_name=CLASS_NAME_STR, + window_size=self.window_size) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Window Size": ["int", 5]} diff --git a/model/filter/filter_1d/remove_outliers.py b/model/filter/filter_1d/remove_outliers.py new file mode 100644 index 0000000..2eac508 --- /dev/null +++ b/model/filter/filter_1d/remove_outliers.py @@ -0,0 +1,67 @@ +import re + +import numpy as np + +from model.filter.filter_regex_helper import float_regex + +CLASS_NAME_STR = "Remove Outliers" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(Outlier Factor: {float_regex}\)$".format(class_name=CLASS_NAME_STR, + float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class RemoveOutliers(metaclass=MC): + def __init__(self, outlier_factor): + self.outlier_factor = outlier_factor + + def get_filtered_image(self, image): + image_filter = image.copy() + z = image_filter[:, 2] + interpolated_z = self.interpolate_outliers(z) + image_filter[:, 2] = interpolated_z + return image_filter + + def interpolate_outliers(self, signal): + outliers_mask = self.find_outliers(signal) + y = signal + y[outliers_mask] = np.nan + nans, x = self.nan_helper(y) + y[nans] = np.interp(x(nans), x(~nans), y[~nans]) + return y + + def nan_helper(self, y): + """Helper to handle indices and logical indices of NaNs. + STOLEN FROM https://stackoverflow.com/questions/6518811/interpolate-nan-values-in-a-numpy-array + Input: + - y, 1d numpy array with possible NaNs + Output: + - nans, logical indices of NaNs + - index, a function, with signature indices= index(logical_indices), + to convert logical indices of NaNs to 'equivalent' indices + Example: + >>> # linear interpolation of NaNs + >>> nans, x= nan_helper(y) + >>> y[nans]= np.interp(x(nans), x(~nans), y[~nans]) + """ + + return np.isnan(y), lambda z: z.nonzero()[0] + + def find_outliers(self, data, m=2): + return abs(data - np.mean(data)) >= self.outlier_factor * np.std(data) + + def __repr__(self): + return "{class_name} (Outlier Factor: {outlier_factor})".format( + class_name=CLASS_NAME_STR, + outlier_factor=self.outlier_factor) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Outlier Factor": ["double", 2.0]} diff --git a/model/filter/filter_1d_organizer.py b/model/filter/filter_1d_organizer.py new file mode 100644 index 0000000..4fb3492 --- /dev/null +++ b/model/filter/filter_1d_organizer.py @@ -0,0 +1,22 @@ +from model.filter.filter_1d import brickwall_bandpass, dc_frequency_remover, \ + butterworth_bandpass, \ + gaussian, brickwall_bandstop, butterworth_bandstop, hann_smoothing, \ + correlation_with_x, \ + correlation_with_y, frequency_remover, frequency_manipulator, remove_outliers +from model.filter.filter_organizer import FilterOrganizer + + +class Filter1DOrganizer(FilterOrganizer): + filter_classes_available = [dc_frequency_remover.DCRemover1D, + remove_outliers.RemoveOutliers, + hann_smoothing.HannSmoothing1D, + gaussian.Gauss1D, + brickwall_bandpass.BrickwallBandPass1D, + brickwall_bandstop.BrickwallBandStop1D, + butterworth_bandpass.ButterworthBandpass1D, + butterworth_bandstop.ButterworthBandstop1D, + correlation_with_x.CorrelationX, + correlation_with_y.CorrelationY, + frequency_remover.FrequencyRemover, + frequency_manipulator.FrequencyManipulator, + ] diff --git a/model/filter/filter_2d/__init__.py b/model/filter/filter_2d/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/filter/filter_2d/adjust_log_filter.py b/model/filter/filter_2d/adjust_log_filter.py new file mode 100644 index 0000000..a3e3dbd --- /dev/null +++ b/model/filter/filter_2d/adjust_log_filter.py @@ -0,0 +1,55 @@ + + +import re + +import numpy as np +from skimage.exposure import adjust_log + +from model.filter.filter_regex_helper import int_regex + +CLASS_NAME_STR = "Adjust Log Filter" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(gain: {int_regex}\)$".format(class_name=CLASS_NAME_STR, + int_regex=int_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class AdjustLogFilter(metaclass=MC): + geometric_shape = "disk_with_hole" + + def __init__(self, gain=1): + self.class_name = CLASS_NAME_STR + self.gain = gain + + @staticmethod + def normalize_image(image): + image -= np.nanmin(image) + image /= np.nanmax(image) + return image + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + norm_image = self.normalize_image(image) + image_back = adjust_log(norm_image, gain=self.gain) + return image_back + + def __repr__(self): + return "{class_name} (gain: {gain})".format( + class_name=CLASS_NAME_STR, + gain=self.gain) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Gain": ["int", 1]} + + @classmethod + def get_mask(cls, *args, **kwargs): + return None diff --git a/model/filter/filter_2d/brickwall_bandpass.py b/model/filter/filter_2d/brickwall_bandpass.py new file mode 100644 index 0000000..b8c2f0b --- /dev/null +++ b/model/filter/filter_2d/brickwall_bandpass.py @@ -0,0 +1,71 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "2D Brickwall Bandpass" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*), max: (\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class BrickwallBandPass2D(metaclass=MC): + geometric_shape = "disk_with_hole" + + def __init__(self, inner_radius=0, outter_radius=100): + self.class_name = CLASS_NAME_STR + self.min_frequency = inner_radius + self.max_frequency = outter_radius + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + fourier_transformed = np.fft.fft2(image) + shifted_fourier_transformed = np.fft.fftshift(fourier_transformed) + rows, cols = image.shape + mask_inner_circle = self.__create_circular_mask(rows, cols, radius=self.min_frequency) + mask_outter_circle = self.__create_circular_mask(rows, cols, radius=self.max_frequency) + mask_bandpass = mask_outter_circle * ~mask_inner_circle + masked_fourier_transformation = shifted_fourier_transformed * mask_bandpass + back_shifted_masked_function = np.fft.ifftshift(masked_fourier_transformation) + img_back = np.fft.ifft2(back_shifted_masked_function) + img_back = img_back.real + return img_back + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + @classmethod + def __create_circular_mask(cls, h, w, center=None, radius=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = dist_from_center <= radius + return mask + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimal Radius": ["int", 0], + "Maximum Radius": ["int", 100]} + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius): + rows, cols = image.shape + mask_inner_circle = cls.__create_circular_mask(rows, cols, radius=inner_radius) + mask_outter_circle = cls.__create_circular_mask(rows, cols, radius=outer_radius) + mask_combined_circles = mask_outter_circle * ~mask_inner_circle + return mask_combined_circles diff --git a/model/filter/filter_2d/brickwall_bandstop.py b/model/filter/filter_2d/brickwall_bandstop.py new file mode 100644 index 0000000..c54c504 --- /dev/null +++ b/model/filter/filter_2d/brickwall_bandstop.py @@ -0,0 +1,73 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "2D Brickwall Bandstop" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*), max: (\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class BrickwallBandStop2D(metaclass=MC): + geometric_shape = "disk_with_hole_inverse" + + def __init__(self, inner_radius=5, outter_radius=10): + self.class_name = CLASS_NAME_STR + self.min_frequency = inner_radius + self.max_frequency = outter_radius + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + fourier_transformed = np.fft.fft2(image) + shifted_fourier_transformed = np.fft.fftshift(fourier_transformed) + rows, cols = image.shape + mask_inner_circle = self.__create_circular_mask(rows, cols, radius=self.min_frequency) + mask_outter_circle = self.__create_circular_mask(rows, cols, radius=self.max_frequency) + mask_bandpass = mask_outter_circle * ~mask_inner_circle + mask_bandstop = ~mask_bandpass + masked_fourier_transformation = shifted_fourier_transformed * mask_bandstop + back_shifted_masked_function = np.fft.ifftshift(masked_fourier_transformation) + img_back = np.fft.ifft2(back_shifted_masked_function) + img_back = img_back.real + return img_back + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency) + + @classmethod + def __create_circular_mask(cls, h, w, center=None, radius=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = dist_from_center <= radius + return mask + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimal Radius": ["int", 10], + "Maximum Radius": ["int", 20]} + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius): + rows, cols = image.shape + mask_inner_circle = cls.__create_circular_mask(rows, cols, radius=inner_radius) + mask_outter_circle = cls.__create_circular_mask(rows, cols, radius=outer_radius) + mask_combined_circles = mask_outter_circle * ~mask_inner_circle + masked_combined_circles_inverse = ~ mask_combined_circles + return masked_combined_circles_inverse diff --git a/model/filter/filter_2d/butterworth_bandpass.py b/model/filter/filter_2d/butterworth_bandpass.py new file mode 100644 index 0000000..7673a90 --- /dev/null +++ b/model/filter/filter_2d/butterworth_bandpass.py @@ -0,0 +1,79 @@ +import re + +import numpy as np +import psychopy.visual.filters + +CLASS_NAME_STR = "2D Butterworth Bandpass" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*), max: (\d*), order: (\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class ButterworthBandPass2D(metaclass=MC): + geometric_shape = "disk_with_hole" + + def __init__(self, inner_radius=0, outter_radius=100, order=5): + self.min_frequency = inner_radius + self.max_frequency = outter_radius + self.order = order + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + cutin = self.min_frequency / image.shape[0] + cutoff = self.max_frequency / image.shape[0] + if cutin <= 0 and cutoff < 1: + butterworth_filter = psychopy.visual.filters.butter2d_lp(size=image.shape, cutoff=cutoff, n=self.order) + elif cutin > 0 and cutoff >= 1: + butterworth_filter = psychopy.visual.filters.butter2d_hp(size=image.shape, cutoff=cutin, n=self.order) + elif cutin > 0 and cutoff < 1: + butterworth_filter = psychopy.visual.filters.butter2d_bp(size=image.shape, cutin=cutin, cutoff=cutoff, + n=self.order) + else: + return image + + image_fft = np.fft.fft2(image) + image_filt = np.fft.fftshift(image_fft) * butterworth_filter + image_back = np.fft.ifft2(np.fft.ifftshift(image_filt)).real + return image_back + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency}, order: {order})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency, + order=self.order) + + @classmethod + def __create_circular_mask(cls, h, w, center=None, radius=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = dist_from_center <= radius + return mask + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimal Radius": ["int", 0], + "Maximum Radius": ["int", 100], + "Order:": ["int", 10], } + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius, order): + rows, cols = image.shape + mask_inner_circle = cls.__create_circular_mask(rows, cols, radius=inner_radius) + mask_outter_circle = cls.__create_circular_mask(rows, cols, radius=outer_radius) + mask_combined_circles = mask_outter_circle * ~mask_inner_circle + return mask_combined_circles diff --git a/model/filter/filter_2d/butterworth_bandstop.py b/model/filter/filter_2d/butterworth_bandstop.py new file mode 100644 index 0000000..13cb6c7 --- /dev/null +++ b/model/filter/filter_2d/butterworth_bandstop.py @@ -0,0 +1,80 @@ +import re + +import numpy as np +import psychopy.visual.filters + +CLASS_NAME_STR = "2D Butterworth Bandstop" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*), max: (\d*), order: (\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class ButterworthBandStop2D(metaclass=MC): + geometric_shape = "disk_with_hole_inverse" + def __init__(self, inner_radius=0, outter_radius=100, order=5): + self.min_frequency = inner_radius + self.max_frequency = outter_radius + self.order = order + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + cutin = self.min_frequency / image.shape[0] + cutoff = self.max_frequency / image.shape[0] + if cutin <= 0 and cutoff < 1: + butterworth_filter = psychopy.visual.filters.butter2d_hp(size=image.shape, cutoff=cutoff, n=self.order) + elif cutin > 0 and cutoff >= 1: + butterworth_filter = psychopy.visual.filters.butter2d_lp(size=image.shape, cutoff=cutin, n=self.order) + elif cutin > 0 and cutoff < 1: + butterworth_filter_lp = psychopy.visual.filters.butter2d_hp(size=image.shape, cutoff=cutoff, n=self.order) + butterworth_filter_hp = psychopy.visual.filters.butter2d_lp(size=image.shape, cutoff=cutin, n=self.order) + butterworth_filter = butterworth_filter_hp + butterworth_filter_lp + else: + return image + + image_fft = np.fft.fft2(image) + image_filt = np.fft.fftshift(image_fft) * butterworth_filter + image_back = np.fft.ifft2(np.fft.ifftshift(image_filt)).real + return image_back + + def __repr__(self): + return "{class_name} (min: {min_frequency}, max: {max_frequency}, order: {order})".format( + class_name=CLASS_NAME_STR, + min_frequency=self.min_frequency, + max_frequency=self.max_frequency, + order=self.order) + + @classmethod + def __create_circular_mask(cls, h, w, center=None, radius=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = dist_from_center <= radius + return mask + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimal Radius": ["int", 0], + "Maximum Radius": ["int", 100], + "Order:": ["int", 10], } + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius, order): + rows, cols = image.shape + mask_inner_circle = cls.__create_circular_mask(rows, cols, radius=inner_radius) + mask_outter_circle = cls.__create_circular_mask(rows, cols, radius=outer_radius) + mask_combined_circles = mask_outter_circle * ~mask_inner_circle + masked_combined_circles_inverse = ~mask_combined_circles + return masked_combined_circles_inverse \ No newline at end of file diff --git a/model/filter/filter_2d/custom_filter.py b/model/filter/filter_2d/custom_filter.py new file mode 100644 index 0000000..3a8e9a7 --- /dev/null +++ b/model/filter/filter_2d/custom_filter.py @@ -0,0 +1,64 @@ + + +import copy +import re + +CLASS_NAME_STR = "Custom Filter" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(filter_code: (.*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + groups = re.match(cls.FILTER_REGEX, class_string).groups() + groups = list(groups) + groups[1] = groups[1].encode('utf-8').decode('unicode_escape') + return groups + + +class CustomFilter(metaclass=MC): + geometric_shape = "disk_with_hole_inverse" + + def __init__(self, filter_code, exception_print_function=print): + self.filter_code: str = filter_code + self._exception_print_function = exception_print_function + + @property + def exception_print_function(self): + try: + return self._exception_print_function + except AttributeError: + self._exception_print_function = print + return self._exception_print_function + + def get_filtered_image(self, image): + global image_back + image_prev = copy.deepcopy(image) + image_back = None + try: + exec("global image_back\n"+self.filter_code, globals(), locals()) + except Exception as e: + self.exception_print_function(str(e)) + return image_prev + if image_back is not None and image_back.shape == image_prev.shape: + return image_back + else: + return image_prev + + def __repr__(self): + filter_code_str = self.filter_code.encode('unicode-escape').decode('utf-8') + return "{class_name} (filter_code: {filter_code})".format( + class_name=CLASS_NAME_STR, filter_code=filter_code_str) + + @classmethod + def necessary_inputs(cls) -> dict: + standard_code = """#image: input image\n#image_back: has to be overwritten and is the output image\nimage_back = image""" + return {"Filter Python Code": ["code", standard_code], } + + @classmethod + def get_mask(cls, *args, **kwargs): + return None diff --git a/model/filter/filter_2d/dc_removal.py b/model/filter/filter_2d/dc_removal.py new file mode 100644 index 0000000..bbc0229 --- /dev/null +++ b/model/filter/filter_2d/dc_removal.py @@ -0,0 +1,29 @@ +import numpy as np +import re +CLASS_NAME_STR = "DC Removal" + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + +class DCRemoval(metaclass=MC): + def get_filtered_image(self, image): + image_mean = np.nanmean(image) + image -= image_mean + return image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/equal_3_point_plane_filter.py b/model/filter/filter_2d/equal_3_point_plane_filter.py new file mode 100644 index 0000000..8d4ce6e --- /dev/null +++ b/model/filter/filter_2d/equal_3_point_plane_filter.py @@ -0,0 +1,82 @@ + + +import re + +import numpy as np + +from model.filter.filter_regex_helper import float_list_regex, float_regex + +CLASS_NAME_STR = "3 Equal Height Point Plane Remover" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Point List: \[({float_list_regex}(?:, {float_list_regex})*)]\)$".format( + class_name=CLASS_NAME_STR, float_list_regex=float_list_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + # TODO + values = re.match(cls.FILTER_REGEX, class_string).groups() + class_name, input_list_str = values + input_list = input_list_str.split(",") + input_list = [input.strip() for input in input_list] + return (class_name, input_list) + + +class Equal3PointPlaneFilter(metaclass=MC): + def __init__(self, point_str_list=None): + self.point_list = [] + for point_str in point_str_list: + point_coordinates_str = re.findall("{float_regex}".format(float_regex=float_regex), + point_str) + print(point_coordinates_str) + point = [] + for point_coordinate_str in point_coordinates_str: + point_coordinate = float(point_coordinate_str) + point.append(point_coordinate) + self.point_list.append(point) + + def get_filtered_image(self, image): + image_filter = image.copy() + points = self.point_list[:3] + for point_index, point in enumerate(points): + z_value = image_filter[int(point[0]), int(point[1])] + points[point_index] = [*point[:2], z_value] + p1, p2, p3 = np.array(points) + # These two vectors are in the plane + v1 = p3 - p1 + v2 = p2 - p1 + + # the cross product is a vector normal to the plane + cp = np.cross(v1, v2) + a, b, c = cp + + # This evaluates a * x3 + b * y3 + c * z3 which equals d + d = np.dot(cp, p3) + + print('The equation is {0}x + {1}y + {2}z = {3}'.format(a, b, c, d)) + + y_bins, x_bins = image_filter.shape + x = np.arange(x_bins) + y = np.arange(y_bins) + X, Y = np.meshgrid(x, y) + + Z = (d - a * X - b * Y) / c + image_filter -= Z + return image_filter + + def __repr__(self): + return "{class_name} (Point List: {point_list})".format( + class_name=CLASS_NAME_STR, + point_list=self.point_list, ) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Point List": ["selected_point_list", 0]} + + @classmethod + def get_mask(cls, *args, **kwargs): + return None diff --git a/model/filter/filter_2d/equalize_adapthist_filter.py b/model/filter/filter_2d/equalize_adapthist_filter.py new file mode 100644 index 0000000..1616845 --- /dev/null +++ b/model/filter/filter_2d/equalize_adapthist_filter.py @@ -0,0 +1,88 @@ + + +import re + +import numpy as np +from skimage.exposure import equalize_adapthist + +from model.filter.filter_regex_helper import int_regex, float_regex + +CLASS_NAME_STR = "Equalize Adapt Hist Filter" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(kernel_size: {int_regex}, clip_limit: {float_regex}, nbins: {int_regex}\)$".format( + class_name=CLASS_NAME_STR, + int_regex=int_regex, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class EqualizeAdaptHistFilter(metaclass=MC): + geometric_shape = "disk_with_hole" + + def __init__(self, kernel_size=35, clip_limit=0.01, nbins=256): + self.class_name = CLASS_NAME_STR + self.kernel_size = kernel_size + self._clip_limit = clip_limit + self._nbins = nbins + + @staticmethod + def normalize_image(image): + image_min = np.nanmin(image) + image -= image_min + image_max = np.nanmax(image) + image /= image_max + return image, image_min, image_max + + @staticmethod + def denormalize_image(image, prev_min, prev_max): + image *= prev_max + image += prev_min + return image + + @property + def clip_limit(self): + try: + return self._clip_limit + except AttributeError: + self._clip_limit = 0.01 + return self._clip_limit + + @property + def nbins(self): + try: + return self._nbins + except AttributeError: + self._nbins = 256 + return self._nbins + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + normalized_image, prev_min, prev_max = self.normalize_image(image) + image_back = equalize_adapthist(normalized_image, kernel_size=self.kernel_size, clip_limit=self.clip_limit, + nbins=self.nbins) + image_back = self.denormalize_image(image_back, prev_min, prev_max) + return image_back + + def __repr__(self): + return "{class_name} (kernel size: {kernel_size}, clip_limit: {clip_limit}, nbins: {nbins})".format( + class_name=CLASS_NAME_STR, + kernel_size=self.kernel_size, + clip_limit=self.clip_limit, + nbins=self.nbins) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Block Size": ["int", 35], + "Clip Limit": ["double", 0.01], + "nbins": ["int", 256]} + + @classmethod + def get_mask(cls, *args, **kwargs): + return None diff --git a/model/filter/filter_2d/erosion.py b/model/filter/filter_2d/erosion.py new file mode 100644 index 0000000..e2f3004 --- /dev/null +++ b/model/filter/filter_2d/erosion.py @@ -0,0 +1,100 @@ +import re + +import numpy as np +import scipy +import skimage +from scipy import ndimage +from skimage import morphology +from skimage.exposure import rescale_intensity + +CLASS_NAME_STR = "Erosion" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(dilation_radius: (\d*), dist: (\d*\.\d*)\)$".format( + class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class Erosion(metaclass=MC): + geometric_shape = None + + def __init__(self, dilation_radius, dist): + self.class_name = CLASS_NAME_STR + self.dilation_radius = dilation_radius + self.max_frequency = dilation_radius + self.dist = dist + + def get_filtered_image(self, image): + image = np.nan_to_num(image.copy()) + dilation_radius = self.dilation_radius + dist = self.dist + even_dist = 2 * (dist // 2) + disksauto = np.zeros((int(2 * (dilation_radius + even_dist)) + + 1, int(2 * (dilation_radius + even_dist)) + 1)) + disksauto[int(dilation_radius + even_dist), int(dilation_radius + even_dist): + int(1.155 / 2 * dist + 2 * dilation_radius + even_dist)] = 1 + disksauto[int(dilation_radius + even_dist - dilation_radius): + int(dilation_radius + even_dist + dilation_radius + 1), + int(1.155 / 2 * dist + dilation_radius + even_dist) - dilation_radius] = 1 + # create rotated crosses, create binary images of these crosses + propeller0blank_binary = disksauto + propeller1blank = rescale_intensity(scipy.ndimage.interpolation.rotate + (propeller0blank_binary, angle=120.0, + reshape=False), 'image', (0, 1)) + propeller1blank_binary = np.zeros(propeller0blank_binary.shape) + propeller1blank_binary[propeller1blank >= np.amax(propeller1blank) / 2] = 1 + + propeller2blank = rescale_intensity(scipy.ndimage.interpolation.rotate + (propeller0blank_binary, angle=-120.0, + reshape=False), 'image', (0, 1)) + propeller2blank_binary = np.zeros(propeller0blank_binary.shape) + propeller2blank_binary[propeller2blank >= np.amax(propeller2blank) / 2] = 1 + + # draw convex hull around binary crosses + propeller0 = skimage.morphology.convex_hull_object(propeller0blank_binary, + neighbors=8) + propeller1 = skimage.morphology.convex_hull_object(propeller1blank_binary, + neighbors=8) + propeller2 = skimage.morphology.convex_hull_object(propeller2blank_binary, + neighbors=8) + + propeller = np.amax([propeller0, propeller1, propeller2], axis=0) + + g0 = propeller + # rotate 3 times in order to get 4 independent orientations of the geometry + g90 = np.rot90(g0) + g180 = np.rot90(g90) + g270 = np.rot90(g180) + + # apply each orientation as an erosion geometry to the STM-file + erode1 = scipy.ndimage.grey_erosion(image, footprint=g0) + erode2 = scipy.ndimage.grey_erosion(image, footprint=g90) + erode3 = scipy.ndimage.grey_erosion(image, footprint=g180) + erode4 = scipy.ndimage.grey_erosion(image, footprint=g270) + + # compare each value and write the minimal value to a new array (mini) + mini = np.amin([erode1, erode2, erode3, erode4], axis=0) + return mini + + def __repr__(self): + return "{class_name} (dilation_radius: {dilation_radius}, dist: {dist})".format( + class_name=CLASS_NAME_STR, + dilation_radius=self.dilation_radius, + dist=self.dist) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Dilation Radius": ["int", 0.1], + "Dist": ["double", 0.1], + } + + @classmethod + def get_mask(cls, *parameters): + return None diff --git a/model/filter/filter_2d/filter_canny_contours.py b/model/filter/filter_2d/filter_canny_contours.py new file mode 100644 index 0000000..847f1cf --- /dev/null +++ b/model/filter/filter_2d/filter_canny_contours.py @@ -0,0 +1,44 @@ +import re + +import numpy as np +import skimage.feature + +CLASS_NAME_STR = "Canny Filter" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(sigma: (\d*\.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class CannyContours(metaclass=MC): + geometric_shape = None + + def __init__(self, sigma=1): + self.sigma = sigma + + def get_filtered_image(self, image): + return_image = image.copy() + minimum, maximum = np.nanmin(image), np.nanmax(image) + image -= minimum + image = (image / maximum) * 255 + image = np.nan_to_num(image) + sharpend_image = skimage.feature.canny(image, sigma=self.sigma) + return_image[sharpend_image] = maximum + return return_image + + def __repr__(self): + return "{class_name} (sigma: {sigma})".format(class_name=CLASS_NAME_STR, sigma=self.sigma) + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return {"Sigma": ["double", 1]} diff --git a/model/filter/filter_2d/filter_fourier_amplitude.py b/model/filter/filter_2d/filter_fourier_amplitude.py new file mode 100644 index 0000000..36e9c0e --- /dev/null +++ b/model/filter/filter_2d/filter_fourier_amplitude.py @@ -0,0 +1,50 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "Fourier Amplitude Filter" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*.\d*), max: (\d*.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class FourierAmplitude(metaclass=MC): + geometric_shape = "disk_with_hole" + + def __init__(self, min_amplitude=0, max_amplitude=1000000000000000000.0): + self.min_amplitude = min_amplitude + self.max_amplitude = max_amplitude + + def get_filtered_image(self, filter_image): + filter_image = np.nan_to_num(filter_image) + fourier_transformed = np.fft.fft2(filter_image) + shifted_fourier_transformed = np.fft.fftshift(fourier_transformed) + filtered_image = shifted_fourier_transformed + filtered_image[abs(filtered_image) <= self.min_amplitude] = 0 + filtered_image[abs(filtered_image) >= self.max_amplitude] = 0 + back_shifted_masked_function = np.fft.ifftshift(filtered_image) + img_back = np.fft.ifft2(back_shifted_masked_function) + img_back = img_back.real + return img_back + + def __repr__(self): + return "{class_name} (min: {min_amplitude}, max: {max_amplitude})".format( + class_name=CLASS_NAME_STR, + min_amplitude=self.min_amplitude, + max_amplitude=self.max_amplitude) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimal Amplitude": ["double", "min_amplitude"], + "Maximum Amplitude": ["double", "max_amplitude"]} + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius): + return None diff --git a/model/filter/filter_2d/filter_plane_removal.py b/model/filter/filter_2d/filter_plane_removal.py new file mode 100644 index 0000000..757ce80 --- /dev/null +++ b/model/filter/filter_2d/filter_plane_removal.py @@ -0,0 +1,112 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "Linear Plane Removal" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class PlaneRemoval(metaclass=MC): + geometric_shape = None + + def get_filtered_image(self, image): + return self.__plane_flatten(image) + + def __get_mean_column_slope(self, array): + """ Return the average column slope. + + This function fits a line through the values of each column and return the + average slope. + + Parameters + ---------- + array : ndarry with ndim = 2 + A two dimensional array with rows and columns. + + Returns + ------- + float + The average column slope + + """ + + number_rows, number_cols = array.shape + + row_vector = np.arange(number_rows) + + col_slopes = np.empty(number_cols) + col_slopes[:] = np.nan + + for i, col in enumerate(array.T): + idx = np.isfinite(col) + finite_col = col[idx] + if finite_col.size: + slope = np.poly1d(np.polyfit(row_vector[idx], finite_col, 1))[1] + col_slopes[i] = slope + + mean_column_slope = col_slopes[~np.isnan(col_slopes)].mean() + + return mean_column_slope + + def __plane_flatten(self, image): + """Determine fit plane and subtract it from image data. + + This function gets the mean flattening plane of the image. + The image is corrected by subtracting this offsets from the image which + results from a tilted sample surface or other measuring artefacts. + + Parameters + ---------- + image : array_like + Greyscale image MxN. + + Returns + ------- + ndarray + Image after plane flattening. + ndarray + Plane. + + Notes + ----- + For a similar solution see: [#]. + + References + ---------- + .. [#] https://pypi.org/project/SPIEPy/ + """ + number_rows, number_cols = image.shape + row_vector = np.arange(number_rows) + col_vector = np.arange(number_cols) + + mean_col_slope = self.__get_mean_column_slope(image) + mean_row_slope = self.__get_mean_column_slope(image.T) + + row, col = np.meshgrid(col_vector, row_vector) + + # add (superpose) planes to get the overall tilted plane + image_plane = row * mean_row_slope + col * mean_col_slope + + # subtract plane from image + flattened_image = image - image_plane + return flattened_image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/flip_x_axis.py b/model/filter/filter_2d/flip_x_axis.py new file mode 100644 index 0000000..09efe4b --- /dev/null +++ b/model/filter/filter_2d/flip_x_axis.py @@ -0,0 +1,32 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "Flip X-Axis" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class FlipXAxis(metaclass=MC): + def get_filtered_image(self, image): + image = np.flip(image, axis=0) + return image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/flip_y_axis.py b/model/filter/filter_2d/flip_y_axis.py new file mode 100644 index 0000000..b2fddac --- /dev/null +++ b/model/filter/filter_2d/flip_y_axis.py @@ -0,0 +1,28 @@ +import numpy as np +import re +CLASS_NAME_STR = "Flip Y-Axis" + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + +class FlipYAxis(metaclass=MC): + def get_filtered_image(self, image): + image = np.flip(image, axis=1) + return image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/fuzzy_contrast_enhancement.py b/model/filter/filter_2d/fuzzy_contrast_enhancement.py new file mode 100644 index 0000000..d04ca6a --- /dev/null +++ b/model/filter/filter_2d/fuzzy_contrast_enhancement.py @@ -0,0 +1,199 @@ + + +import math +import re + +import numpy as np + +from model.filter.filter_regex_helper import int_regex, float_regex + +CLASS_NAME_STR = "Fuzzy Contrast Enhancement" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(n: {int_regex}, m: {int_regex}, epsilon: {float_regex}, gamma: {float_regex}, ideal_variance: {float_regex}\)$".format( + class_name=CLASS_NAME_STR, + int_regex=int_regex, float_regex=float_regex)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class FuzzyContrastEnhancement(metaclass=MC): + def __init__(self, n=2, m=2, epsilon=0.00001, gamma=1, ideal_variance=0.35): + self.n = n + self.m = m + self.epsilon = epsilon + self.gamma = gamma + self.ideal_variance = ideal_variance + + # split the image to windows + def phy(self, value): # phy: E --> R + # if ((1+value)/((1-value)+0.0001)) < 0: + # print(value) + return 0.5 * np.log((1 + value) / ((1 - value) + self.epsilon)) + + def multiplication(self, value1, value2): # ExE --> R + return self.phy(value1) * self.phy(value2) + + def norm(self, value): + return abs(self.phy(value)) + + def scalar_multiplication(self, scalar, value): # value in E ([-1,1]) + s = (1 + value) ** scalar + z = (1 - value) ** scalar + res = (s - z) / (s + z + self.epsilon) + return res + + def addition(self, value1, value2): # value1,value2 are in E ([-1,1]) + res = (value1 + value2) / (1 + (value1 * value2) + self.epsilon) + return res + + def subtract(self, value1, value2): # value1,value2 are in E ([-1,1]) + res = (value1 - value2) / (1 - (value1 * value2) + self.epsilon) + return res + + def C(self, m, i): + return math.factorial(m) / ((math.factorial(i) * math.factorial(m - i)) + self.epsilon) + + def qx(self, i, x): # i: window index in rows, x: number of current pixel on x-axis + if (x == self.WIDTH - 1): + return 0 + return self.C(self.m, i) * ( + np.power((x - self.x0) / (self.x1 - x), i) * np.power((self.x1 - x) / (self.x1 - self.x0), + self.m)) # This is the seconf implementation + + def qy(self, j, y): + ''' + The second implementation for the formula does not go into overflow. + ''' + if (y == self.HEIGHT - 1): + return 0 + return self.C(self.n, j) * ( + np.power((y - self.y0) / (self.y1 - y), j) * np.power((self.y1 - y) / (self.y1 - self.y0), + self.n)) # This is the seconf implementation + + def p(self, i, j, x, y): + return self.qx(i, x) * self.qy(j, y) + + def mapping(self, img, source, dest): + return (dest[1] - dest[0]) * ((img - source[0]) / (source[1] - source[0])) + dest[0] + + def cal_ps_ws(self, m, n, w, h, gamma): + ps = np.zeros((m, n, w, h)) + for i in range(m): + for j in range(n): + for k in range(w): + for l in range(h): + ps[i, j, k, l] = self.p(i, j, k, l) + + ws = np.zeros((m, n, w, h)) + for i in range(m): + for j in range(n): + ps_power_gamma = np.power(ps[i, j], gamma) + for k in range(w): + for l in range(h): + ws[i, j, k, l] = ps_power_gamma[k, l] / (np.sum(ps[:, :, k, l]) + self.epsilon) + return ps, ws + + def cal_means_variances_lamdas(self, m, n, ws, e_layer): + means = np.zeros((m, n)) + variances = np.zeros((m, n)) + lamdas = np.zeros((m, n)) + + def window_card(w): + return np.sum(w) + + def window_mean(w, i, j): + mean = 0 + for k in range(self.HEIGHT): + for l in range(self.WIDTH): + mean = self.addition(mean, self.scalar_multiplication(w[i, j, l, k], e_layer[k, l])) + mean /= window_card(w[i, j]) + return mean + + def window_variance(w, i, j): + variance = 0 + for k in range(self.HEIGHT): + for l in range(self.WIDTH): + variance += w[i, j, l, k] * np.power(self.norm(self.subtract(e_layer[k, l], means[i, j])), 2) + variance /= window_card(w[i, j]) + return variance + + def window_lamda(w, i, j): + return np.sqrt(self.ideal_variance) / (np.sqrt(variances[i, j]) + self.epsilon) + + def window_tao(w, i, j): + return window_mean(w, i, j) + + for i in range(m): + for j in range(n): + means[i, j] = window_mean(ws, i, j) + variances[i, j] = window_variance(ws, i, j) + lamdas[i, j] = window_lamda(ws, i, j) + taos = means.copy() + return means, variances, lamdas, taos + + def window_enh(self, w, i, j, e_layer): + return self.scalar_multiplication(self.lamdas[i, j], self.subtract(e_layer, self.taos[i, j])) + + def image_enh(self, m, n, w, e_layer): + new_image = np.zeros(e_layer.shape) + width = e_layer.shape[1] + height = e_layer.shape[0] + for i in range(m): + for j in range(n): + win = self.window_enh(w, i, j, e_layer) + w1 = w[i, j].T.copy() + for k in range(width): + for l in range(height): + new_image[l, k] = self.addition(new_image[l, k], + self.scalar_multiplication(w1[l, k], win[l, k])) + return new_image + + def one_layer_enhacement(self, e_layer): + # card_image = layer.shape[0]*layer.shape[1] + new_E_image = self.image_enh(self.m, self.n, self.ws, e_layer) + res_image = self.mapping(new_E_image, (-1, 1), (self.prev_min, self.prev_max)) + res_image = np.round(res_image) + res_image = res_image.astype(np.uint8) + return res_image + + def get_filtered_image(self, image): + self.HEIGHT, self.WIDTH = image.shape + image = np.nan_to_num(image) + self.x0, self.x1, self.y0, self.y1 = 0, self.WIDTH - 1, 0, self.HEIGHT - 1 + self.ps, self.ws = self.cal_ps_ws(self.m, self.n, self.WIDTH, self.HEIGHT, self.gamma) + self.prev_min, self.prev_max = image.min(), image.max() + e_layer = self.mapping(image, (self.prev_min, self.prev_max), (-1, 1)) + self.means, self.variances, self.lamdas, self.taos = self.cal_means_variances_lamdas(self.m, self.n, self.ws, + e_layer) + image_back = self.one_layer_enhacement(e_layer) + image_back = image_back.astype("float") + return image_back + + def __repr__(self): + return "{class_name} (n: {n}, m: {m}, epsilon: {epsilon}, gamma: {gamma}, ideal_variance: {ideal_variance})".format( + class_name=CLASS_NAME_STR, + n=self.n, + m=self.m, + epsilon=self.epsilon, + gamma=self.gamma, + ideal_variance=self.ideal_variance + ) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Number of Rows": ["int", 2], # number of rows (windows on columns) + "Number of columns": ["int", 2], # number of columns (windows on rows) + "Epsilon": ["double", 0.00001], + "Gamma": ["double", 1], + "Ideal Variance": ["double", 0.35], } + + @classmethod + def get_mask(cls, *args, **kwargs): + return None diff --git a/model/filter/filter_2d/gaussian.py b/model/filter/filter_2d/gaussian.py new file mode 100644 index 0000000..964561d --- /dev/null +++ b/model/filter/filter_2d/gaussian.py @@ -0,0 +1,44 @@ +import re + +import numpy as np +import scipy.ndimage + +CLASS_NAME_STR = "2D Gaussian" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(sigma: (\d*\.\d*), truncate: (\d*\.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class Gauss2D(metaclass=MC): + + def __init__(self, sigma=1.0, truncate=4.0): + self.sigma = sigma + self.truncate = truncate + + def get_filtered_image(self, image): + image_boolean = 0 * image.copy() + 1 + image_boolean[np.isnan(image)] = 0 + image_boolean_gaussian = scipy.ndimage.gaussian_filter(image_boolean, sigma=self.sigma, truncate=self.truncate) + image = np.nan_to_num(image) + image_gaussian = scipy.ndimage.gaussian_filter(image, self.sigma, truncate=self.truncate) + image_normalized = image_gaussian / image_boolean_gaussian + return image_normalized + + def __repr__(self): + return "{class_name} (sigma: {sigma}, truncate: {truncate})".format(class_name=CLASS_NAME_STR, sigma=self.sigma, + truncate=self.truncate) + + def get_mask(self, *parameters): + return None + + @classmethod + def necessary_inputs(cls) -> dict: + return {"sigma": ["double", 1], + "truncate": ["double", 4]} diff --git a/model/filter/filter_2d/hexagon_structure.py b/model/filter/filter_2d/hexagon_structure.py new file mode 100644 index 0000000..2fc46c0 --- /dev/null +++ b/model/filter/filter_2d/hexagon_structure.py @@ -0,0 +1,79 @@ +import re + +import cv2 +import numpy as np +from scipy import ndimage + +CLASS_NAME_STR = "2D Hexagon Structure" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class HexagonStructure(metaclass=MC): + def get_filtered_image(self, image: np.ndarray): + rows, cols = image.shape + image = np.nan_to_num(image) + image /= image.max() + image -= image.min() + image -= image.mean() + image_fft = np.fft.fft2(image) + image_filt = np.fft.fftshift(image_fft) + image_back = abs(image_filt) + data = image_back + neighborhood_size = int(rows / 50) + threshold = 500 + data_max = ndimage.filters.maximum_filter(data, neighborhood_size) + maxima = (data == data_max) + data_min = ndimage.filters.minimum_filter(data, neighborhood_size) + diff = ((data_max - data_min) > threshold) + maxima[diff == 0] = 0 + image_back = maxima + return image_back + + def __repr__(self): + return "{class_name}".format( + class_name=CLASS_NAME_STR) + + def __create_circular_mask(cls, h, w, center=None, radius=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = dist_from_center <= radius + return mask + + @classmethod + def necessary_inputs(cls) -> dict: + return {} + + @classmethod + def get_mask(cls): + return None + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + img = cv2.imread('test.png', 0) + img = img.astype(float) + bandpass = HexagonStructure() + bandpass.min_frequency = 10 + bandpass.max_frequency = 100 + image = bandpass.get_filtered_image(img) + plt.subplot(121), plt.imshow(img, cmap='gray') + plt.title('Input'), plt.xticks([]), plt.yticks([]) + plt.subplot(122), plt.imshow(image, cmap='gray') + plt.show() diff --git a/model/filter/filter_2d/invert_image.py b/model/filter/filter_2d/invert_image.py new file mode 100644 index 0000000..4a406ab --- /dev/null +++ b/model/filter/filter_2d/invert_image.py @@ -0,0 +1,32 @@ +import numpy as np +import re +CLASS_NAME_STR = "Invert Image" + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + +class InvertImage(metaclass=MC): + def get_filtered_image(self, image): + image = image.copy() + tmp_min = np.nanmin(image) + image -= tmp_min + image = np.nanmax(image) - image + image += tmp_min + return image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/local_maxima_filter.py b/model/filter/filter_2d/local_maxima_filter.py new file mode 100644 index 0000000..8f00a16 --- /dev/null +++ b/model/filter/filter_2d/local_maxima_filter.py @@ -0,0 +1,50 @@ +import re +from scipy import ndimage +import numpy as np + +CLASS_NAME_STR = "Local Maxima Filter(Fourier)" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(Neighborhood Size: (\d*), Threshold: (\d*.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class LocalMaximaFilter(metaclass=MC): + def __init__(self, neighborhood_size=6.0, threshold=10.0): + self.neighborhood_size = neighborhood_size + self.threshold = threshold + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + image_fft = np.fft.fft2(image) + image_filt = np.fft.fftshift(image_fft) + image_back = abs(image_filt) + data_max = ndimage.filters.maximum_filter(image_back, self.neighborhood_size) + maxima = (image_back == data_max) + data_min = ndimage.filters.minimum_filter(image_back, self.neighborhood_size) + diff = ((data_max - data_min) > self.threshold) + maxima[diff == 0] = 0 + image_filt = maxima + image_back = np.fft.ifft2(np.fft.ifftshift(image_filt)).real + return image_back + + def __repr__(self): + return "{class_name} (Neighborhood Size: {neighborhood_size}, Threshold: {threshold})".format( + class_name=CLASS_NAME_STR, + neighborhood_size=self.neighborhood_size, + threshold=self.threshold) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Neighborhood Size": ["int", 6], + "Threshold": ["double", 10]} + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius): + return None diff --git a/model/filter/filter_2d/percentile_filter.py b/model/filter/filter_2d/percentile_filter.py new file mode 100644 index 0000000..06f9d1f --- /dev/null +++ b/model/filter/filter_2d/percentile_filter.py @@ -0,0 +1,43 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "Percentile Filter" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name}) \(min: (\d*.\d*), max: (\d*.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class PercentileFilter(metaclass=MC): + def __init__(self, min_percentile=0, max_percentile=100.0): + self.min_percentile = min_percentile + self.max_percentile = max_percentile + + def get_filtered_image(self, filter_image): + percentile = np.percentile(filter_image[~np.isnan(filter_image)], (self.min_percentile,self.max_percentile)) + filter_image[filter_image < percentile[0]] = percentile[0] + filter_image[filter_image > percentile[1]] = percentile[1] + img_back = filter_image + return img_back + + def __repr__(self): + return "{class_name} (min: {min_percentile}, max: {max_percentile})".format( + class_name=CLASS_NAME_STR, + min_percentile=self.min_percentile, + max_percentile=self.max_percentile) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Minimum Percentile": ["double", 0], + "Maximum Percentile": ["double", 100]} + + @classmethod + def get_mask(cls, image, inner_radius, outer_radius): + return None diff --git a/model/filter/filter_2d/plane_removal_one_for_all.py b/model/filter/filter_2d/plane_removal_one_for_all.py new file mode 100644 index 0000000..6027b10 --- /dev/null +++ b/model/filter/filter_2d/plane_removal_one_for_all.py @@ -0,0 +1,114 @@ +import re + +import numpy as np + +CLASS_NAME_STR = "First Image Plane Removal" + + +class MC(type): + FILTER_REGEX = re.compile(r"^({class_name})$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class PlaneRemovalOneForAll(metaclass=MC): + geometric_shape = None + plane = None + + def get_filtered_image(self, image): + return self.__plane_flatten(image) + + def __get_mean_column_slope(self, array): + """ Return the average column slope. + + This function fits a line through the values of each column and return the + average slope. + + Parameters + ---------- + array : ndarry with ndim = 2 + A two dimensional array with rows and columns. + + Returns + ------- + float + The average column slope + + """ + + number_rows, number_cols = array.shape + + row_vector = np.arange(number_rows) + + col_slopes = np.empty(number_cols) + col_slopes[:] = np.nan + + for i, col in enumerate(array.T): + idx = np.isfinite(col) + finite_col = col[idx] + if finite_col.size: + slope = np.poly1d(np.polyfit(row_vector[idx], finite_col, 1))[1] + col_slopes[i] = slope + + mean_column_slope = col_slopes[~np.isnan(col_slopes)].mean() + + return mean_column_slope + + def __plane_flatten(self, image): + """Determine fit plane and subtract it from image data. + + This function gets the mean flattening plane of the image. + The image is corrected by subtracting this offsets from the image which + results from a tilted sample surface or other measuring artefacts. + + Parameters + ---------- + image : array_like + Greyscale image MxN. + + Returns + ------- + ndarray + Image after plane flattening. + ndarray + Plane. + + Notes + ----- + For a similar solution see: [#]. + + References + ---------- + .. [#] https://pypi.org/project/SPIEPy/ + """ + if self.plane is None: + number_rows, number_cols = image.shape + row_vector = np.arange(number_rows) + col_vector = np.arange(number_cols) + + mean_col_slope = self.__get_mean_column_slope(image) + mean_row_slope = self.__get_mean_column_slope(image.T) + + row, col = np.meshgrid(col_vector, row_vector) + + # add (superpose) planes to get the overall tilted plane + image_plane = row * mean_row_slope + col * mean_col_slope + self.plane = image_plane + # subtract plane from image + flattened_image = image - self.plane + return flattened_image + + def __repr__(self): + return CLASS_NAME_STR + + @classmethod + def get_mask(cls, *parameters): + return None + + @classmethod + def necessary_inputs(cls): + return dict() diff --git a/model/filter/filter_2d/principal_component_analysis.py b/model/filter/filter_2d/principal_component_analysis.py new file mode 100644 index 0000000..be1eec1 --- /dev/null +++ b/model/filter/filter_2d/principal_component_analysis.py @@ -0,0 +1,42 @@ +import re + +import numpy as np +from sklearn.decomposition import PCA + +CLASS_NAME_STR = "2D Principal Component Analysis" + + +class MC(type): + FILTER_REGEX = re.compile( + "^({class_name}) \(Preserve Variance in Percent: (\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class PrincipalComponentAnalysis(metaclass=MC): + def __init__(self, preserve_variance_percent=90.0): + self.preserve_variance_percent = preserve_variance_percent + + def get_filtered_image(self, image): + image = np.nan_to_num(image) + pca = PCA(self.preserve_variance_percent / 100).fit(image) + component = pca.transform(image) + filtered_image = pca.inverse_transform(component) + return filtered_image + + def __repr__(self): + return "{class_name} (Preserve Variance in Percent: {preserve_variance})".format( + class_name=CLASS_NAME_STR, + preserve_variance=self.preserve_variance_percent) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Preserve Variance in Percent": ["double", 90]} + + @classmethod + def get_mask(cls, *parameters): + return None diff --git a/model/filter/filter_2d/rotate_image.py b/model/filter/filter_2d/rotate_image.py new file mode 100644 index 0000000..0cfb821 --- /dev/null +++ b/model/filter/filter_2d/rotate_image.py @@ -0,0 +1,39 @@ +import math +import re +import numpy as np + +CLASS_NAME_STR = "Rotate Image (90° steps)" + + +class MC(type): + FILTER_REGEX = re.compile( + r"^({class_name}) \(Degree: (\d*\.\d*)\)$".format(class_name=CLASS_NAME_STR)) + + def __repr__(cls): + return CLASS_NAME_STR + + def check_string(cls, class_string): + return re.match(cls.FILTER_REGEX, class_string).groups() + + +class RotateImageFilter(metaclass=MC): + def __init__(self, degree): + self.degree = degree + + def get_filtered_image(self, image): + number_of_rotations = math.ceil(self.degree / 90) + image_back = np.rot90(image, number_of_rotations) + return image_back + + def __repr__(self): + return "{class_name} (Degree: {degree})".format( + class_name=CLASS_NAME_STR, + degree=self.degree) + + @classmethod + def necessary_inputs(cls) -> dict: + return {"Degree": ["double", 90]} + + @classmethod + def get_mask(cls, *args): + return None diff --git a/model/filter/filter_2d_organizer.py b/model/filter/filter_2d_organizer.py new file mode 100644 index 0000000..4e0e194 --- /dev/null +++ b/model/filter/filter_2d_organizer.py @@ -0,0 +1,42 @@ +from model.filter.filter_2d import filter_plane_removal, brickwall_bandpass, filter_fourier_amplitude, \ + filter_canny_contours, gaussian, dc_removal, \ + brickwall_bandstop, percentile_filter, \ + principal_component_analysis, local_maxima_filter, invert_image, \ + butterworth_bandstop, butterworth_bandpass, flip_x_axis, flip_y_axis, plane_removal_one_for_all, erosion, \ + equalize_adapthist_filter, adjust_log_filter, custom_filter, rotate_image, equal_3_point_plane_filter, \ + fuzzy_contrast_enhancement +from model.filter.filter_organizer import FilterOrganizer + + +class Filter2DOrganizer(FilterOrganizer): + filter_classes_available = [invert_image.InvertImage, + flip_x_axis.FlipXAxis, + flip_y_axis.FlipYAxis, + rotate_image.RotateImageFilter, + dc_removal.DCRemoval, + filter_plane_removal.PlaneRemoval, + equal_3_point_plane_filter.Equal3PointPlaneFilter, + plane_removal_one_for_all.PlaneRemovalOneForAll, + fuzzy_contrast_enhancement.FuzzyContrastEnhancement, + adjust_log_filter.AdjustLogFilter, + equalize_adapthist_filter.EqualizeAdaptHistFilter, + erosion.Erosion, + filter_fourier_amplitude.FourierAmplitude, + gaussian.Gauss2D, + filter_canny_contours.CannyContours, + percentile_filter.PercentileFilter, + principal_component_analysis.PrincipalComponentAnalysis, + local_maxima_filter.LocalMaximaFilter, + brickwall_bandpass.BrickwallBandPass2D, + butterworth_bandpass.ButterworthBandPass2D, + brickwall_bandstop.BrickwallBandStop2D, + butterworth_bandstop.ButterworthBandStop2D, + custom_filter.CustomFilter, + ] + + def get_filter_mask(self, filter_class_index, parameters): + if filter_class_index >= 0: + filter_class = self.filter_classes_available[filter_class_index] + return filter_class.get_mask(*parameters) + else: + return None diff --git a/model/filter/filter_file_parser.py b/model/filter/filter_file_parser.py new file mode 100644 index 0000000..373715c --- /dev/null +++ b/model/filter/filter_file_parser.py @@ -0,0 +1,35 @@ +from model.data import scan_organizier + + +class FilterFileParser: + @classmethod + def load_from_text_file(cls, file_path): + file = open(file_path, "r") + file_lines = file.readlines() + filter_category = None + filter_1d_full_signal = "" + filter_1d_string = "" + filter_2d_string = "" + for line in file_lines: + line = line.strip() + if line == "__1D Full Signal Filters__": + filter_category = "1d_full_signal" + elif line == "__1D Filters__": + filter_category = "1d" + elif line == "__2D Filters__": + filter_category = "2d" + if filter_category == "1d_full_signal": + filter_1d_full_signal += line + "\n" + if filter_category == "1d": + filter_1d_string += line + "\n" + elif filter_category == "2d": + filter_2d_string += line + "\n" + + scan_organizier.ScanOrganizer.current_scan.filter_1d_organizer.generate_filter_list_from_string( + filter_1d_string) + scan_organizier.ScanOrganizer.current_scan.filter_2d_organizer.generate_filter_list_from_string( + filter_2d_string) + scan_organizier.ScanOrganizer.current_scan.filter_1d_full_signal_organizer.generate_filter_list_from_string( + filter_1d_full_signal) + scan_organizier.ScanOrganizer.current_scan.apply_1d_filters(apply_full_signal_filters=True) + scan_organizier.ScanOrganizer.current_scan.apply_2d_filters() diff --git a/model/filter/filter_organizer.py b/model/filter/filter_organizer.py new file mode 100644 index 0000000..a9492d2 --- /dev/null +++ b/model/filter/filter_organizer.py @@ -0,0 +1,136 @@ +import os + +import yaml + + +def load_saved_filter(): + try: + saved_filters = yaml.safe_load(open("saved_filters/saved_filters.yaml")) + except FileNotFoundError: + saved_filters = None + if saved_filters is None: + saved_filters = {} + return saved_filters + + +class FilterOrganizer: + filter_classes_available = [] + _saved_filters = load_saved_filter() + + def __init__(self): + self.filters = list() + + @classmethod + def get_saved_filters(cls): + try: + return cls._saved_filters + except AttributeError: + cls._saved_filters = cls.load_saved_filter() + return cls._saved_filters + + def add_filter_by_index(self, filter_class_index, parameters=None): + if parameters is None: + parameters = list() + filter_class = self.get_filter_class_by_index(filter_class_index) + filter_instance = filter_class(*parameters) + self.filters.append(filter_instance) + + def remove_filter(self, filter_index_to_remove): + del self.filters[filter_index_to_remove] + + def get_filtered_image(self, image): + for filter in self.filters: + image = filter.get_filtered_image(image) + return image + + def get_filter_class_by_index(self, filter_class_index): + filter_class = self.filter_classes_available[filter_class_index] + return filter_class + + def get_filters_inputs(self, filter_class_index) -> dict: + if filter_class_index < 0: + return {} + else: + filter_class = self.filter_classes_available[filter_class_index] + return filter_class.necessary_inputs() + + def generate_filter_from_line(self, line: str): + for filter_class in self.filter_classes_available: + try: + filter_string_matches = filter_class.check_string(line) + except AttributeError: + continue + if filter_string_matches is not None: + filter_class_inputs = list() + for string_matches in filter_string_matches: + try: + if type(string_matches) == str: + filter_class_inputs.append(self.cast_string_to_int_or_float(string_matches)) + elif type(string_matches) == list: + filter_class_inputs.append(self.cast_list_to_int_or_float(string_matches)) + except ValueError: + if str(filter_class) != string_matches: + filter_class_inputs.append(string_matches) + self.filters.append(filter_class(*filter_class_inputs)) + break + + def cast_list_to_int_or_float(self, input_list): + output_list = list() + for input in input_list: + output_list.append(self.cast_string_to_int_or_float(input)) + return output_list + + def cast_string_to_int_or_float(self, input_string): + try: + return int(input_string) + except ValueError: + return float(input_string) + + def generate_filter_list_from_string(self, filter_list_string: str): + self.filters = list() + for line in filter_list_string.splitlines(): + line = line.strip() + self.generate_filter_from_line(line) + + def __repr__(self): + return_string = "" + for filter in self.filters: + return_string += r"{}".format(filter) + "\n" + return return_string + + @classmethod + def add_filter_to_saved_filters(cls, filter_category, filter_class, filter_name, filter_inputs): + if filter_category not in cls.get_saved_filters(): + cls.get_saved_filters()[filter_category] = {} + if filter_class not in cls.get_saved_filters()[filter_category]: + cls.get_saved_filters()[filter_category][filter_class] = {} + cls.get_saved_filters()[filter_category][filter_class][filter_name] = filter_inputs + cls.save_filter_settings() + + @classmethod + def get_saved_filter_names(cls, filter_category, filter_class): + try: + return list(cls.get_saved_filters()[filter_category][filter_class].keys()) + except KeyError: + return [] + + @classmethod + def get_saved_filter_inputs(cls, filter_category, filter_class, filter_name): + try: + return cls.get_saved_filters()[filter_category][filter_class][filter_name] + except KeyError: + return {} + + @classmethod + def load_saved_filter(cls): + return load_saved_filter() + + @classmethod + def save_filter_settings(cls): + if not os.path.isdir("saved_filters"): + os.mkdir("saved_filters") + config_file_name = "saved_filters/saved_filters.yaml" + + if os.path.exists(config_file_name): + os.remove(config_file_name) + yaml.safe_dump(cls.get_saved_filters(), open(config_file_name, "w"), default_flow_style=False, sort_keys=False) diff --git a/model/filter/filter_regex_helper.py b/model/filter/filter_regex_helper.py new file mode 100644 index 0000000..a386880 --- /dev/null +++ b/model/filter/filter_regex_helper.py @@ -0,0 +1,3 @@ +float_regex = r"-?\d+(?:\.\d+)?(?:e-?\d+)?" +int_regex = r"-?\d*" +float_list_regex = fr"\[({float_regex}(?:, {float_regex})*)\]" diff --git a/model/saasmi_utils/__init__.py b/model/saasmi_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/saasmi_utils/area_analyser.py b/model/saasmi_utils/area_analyser.py new file mode 100644 index 0000000..4766f0c --- /dev/null +++ b/model/saasmi_utils/area_analyser.py @@ -0,0 +1,92 @@ +import networkx as nx +import numpy as np +from PIL import Image + +from utilities.images import numpy_array_drawer + + +class SaasmiAnalyserArea: + def __init__(self): + self._area_list = [] + self._input_mask_and_file_path_list = [] + + def add_polygon(self, polygon, area_type): + self.area_list.append([polygon, area_type]) + + @property + def area_list(self): + try: + return self._area_list + except AttributeError: + self._area_list = [] + return self._area_list + + @property + def input_mask_and_file_path_list(self): + try: + return self._input_mask_and_file_path_list + except AttributeError: + self._input_mask_and_file_path_list = [] + return self.input_mask_and_file_path_list + + def get_subgraph_inside_image_mask(self, graph, polygon_image_mask): + coordinate_dict = nx.get_node_attributes(graph, 'marker') + selected_nodes = {key: value for key, value in coordinate_dict.items() if + not polygon_image_mask[tuple(map(int, value))] == 0} + sub_graph = graph.subgraph(selected_nodes) + return sub_graph + + def get_node_ids_inside_image_mask(self, graph, polygon_image_mask): + coordinate_dict = nx.get_node_attributes(graph, 'marker') + selected_nodes = [key for key, value in coordinate_dict.items() if + not polygon_image_mask[tuple(map(int, value))] == 0] + return selected_nodes + + def generate_image_mask_by_input_mask(self, image_shape): + image_mask = np.array(Image.new('L', image_shape, 1), dtype=bool).T + for input_mask, file_path in self.input_mask_and_file_path_list: + image_mask = np.logical_and(input_mask, image_mask) + + return image_mask + + def generate_image_mask_by_polygon(self, image_shape): + polygon_image_mask = None + for polygon, area_type in self.area_list: + if area_type == "negative": + if polygon_image_mask is None: + polygon_image_mask = np.array(Image.new('L', image_shape, 1), dtype=bool).T + new_mask = numpy_array_drawer.get_polygon_mask_array(image_shape, + polygon, + mask_type_positiv=False) + polygon_image_mask = np.logical_and(polygon_image_mask, new_mask) + else: + if polygon_image_mask is None: + polygon_image_mask = np.array(Image.new('L', image_shape, 0), dtype=bool).T + new_mask = numpy_array_drawer.get_polygon_mask_array(polygon_image_mask.shape, + polygon, + mask_type_positiv=True) + polygon_image_mask = np.logical_or(polygon_image_mask, new_mask) + if polygon_image_mask is None: + polygon_image_mask = np.array(Image.new('L', image_shape, 1), dtype=bool).T + return polygon_image_mask + + def generate_sub_graph(self, image_shape, rag): + image_mask = self.get_image_mask(image_shape) + sub_graph = self.get_subgraph_inside_image_mask(rag, image_mask) + self.sub_graph = sub_graph + self._image_mask = image_mask + return sub_graph + + def add_input_image_mask(self, image_mask, file_path): + self.input_mask_and_file_path_list.append((image_mask, file_path)) + + def get_image_mask(self, image_shape=None): + try: + polygon_image_mask = self.generate_image_mask_by_polygon(image_shape) + input_image_mask = self.generate_image_mask_by_input_mask(image_shape) + image_mask = np.logical_and(polygon_image_mask, input_image_mask) + self._image_mask = image_mask + return self._image_mask + except AttributeError: + self._image_mask = np.array(Image.new('L', image_shape, 1), dtype=bool).T + return self._image_mask diff --git a/model/saasmi_utils/graph.py b/model/saasmi_utils/graph.py new file mode 100644 index 0000000..b23468e --- /dev/null +++ b/model/saasmi_utils/graph.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +"""This module provides a graph data structure.""" + +from functools import partial + +import networkx as nx + +from model.saasmi_utils.utils import polar_coords + + +class Graph(nx.Graph): + """A graph. + + This class extends :class:`networkx.Graph`. + + Parameters + ---------- + incoming_graph_data : input graph (optional, default: None) + Data to initialize graph. If None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + embedding : dict, optional Combinatorial embedding of the nodes. + *kwargs Keyword arguments passed to the :class:`networkx.Graph` + constructor. + + """ + + def __init__(self, incoming_graph_data=None, embedding=None, **kwargs): + super(Graph, self).__init__(incoming_graph_data, **kwargs) + self.embedding = embedding + + @property + def embedding(self): + """:obj:`dict`: Comibinatorial embedding of the graph.""" + return self._embedding + + @embedding.setter + def embedding(self, value): + if value is None: + value = {} + if len(value) != 0 and set(value.keys()) != set(self.nodes()): + raise ValueError("Embedding does not match nodes.") + self._embedding = value + + def embed(self, coords): + """Return a comibinatorial embedding given a set of coordinates. + + Assigning the 2D coordinates ``coords`` to the nodes, this method + return a combinatorial embedding. + + Parameters + ---------- + coords : ndarray with shape (N, 2) + A list of N 2D coordinates. + + Returns + ------- + dict + Dictionary with nodes as keys and a list of + neighbors in clockwise direction as values. + + """ + # coords = np.asarray(coords) + # if coords.shape[1] != 2: + # raise ValueError("2D-coordinates required") + + cyclic_ordered_neighbors = {} + for node in self.nodes: + neighbors = list(self.adj[node]) + # neighbor_coords = coords[neighbors] + neighbor_coords = [coords[neighbor] for neighbor in neighbors] + if len(neighbors): + sort_function = partial( + polar_coords, + pole=coords[node], + polar_axis=neighbor_coords[0], + ) + sorted_indices = [ + x + for x, y in sorted( + enumerate(neighbor_coords), + key=lambda x: sort_function(x[1]), + ) + ] + sorted_neighbors = [neighbors[i] for i in sorted_indices] + else: + sorted_neighbors = None + cyclic_ordered_neighbors[node] = sorted_neighbors + self.embedding = cyclic_ordered_neighbors + + def face_cycles(self, embedding=None, bounded=True): + """Return the faces of an embedded graph. + + This method returns the face cycles of an embedded graph. + + Parameters + ---------- + embedding : dict, optional + Dictionary with nodes as keys and a list of each node's + neighbors in clockwise order as values. + bounded : Boolean, optional + If ``True`` only return the bounded face cycles, if ``False`` also + return the unbounded face cycle. + + Returns + ------- + list + List with the face cycles. Each face cycle is given as a Subgraph + of the graph. + + """ + if embedding is None: + embedding = self.embedding + else: + self.embedding = embedding + + if len(self) == 0: + return [] + + if embedding is None: + raise ValueError("Unvalid embedding") + + directed_edges = set(self.to_directed().edges) + + faces = [] + path = [] + edge = directed_edges.pop() + path.append(edge) + + while len(directed_edges) > 0: + neighbors = embedding[path[-1][-1]] + next_node = neighbors[ + (neighbors.index(path[-1][-2]) + 1) % (len(neighbors)) + ] + tup = (path[-1][-1], next_node) + if tup == path[0]: + faces.append(self.edge_subgraph(path)) + path = [] + edge = directed_edges.pop() + path.append(edge) + else: + path.append(tup) + directed_edges.remove(tup) + if len(path) != 0: + faces.append(self.edge_subgraph(path)) + + # Remove unbounded face + if bounded: + faces.remove(max(faces, key=len)) + + return faces diff --git a/model/saasmi_utils/saasmi.py b/model/saasmi_utils/saasmi.py new file mode 100644 index 0000000..15aa920 --- /dev/null +++ b/model/saasmi_utils/saasmi.py @@ -0,0 +1,547 @@ +# -*- coding: utf-8 -*- +"""Segmentation tools for sxm images.""" +import math +import multiprocessing + +import networkx as nx +import numpy as np +import scipy +from scipy import spatial +from scipy.ndimage import distance_transform_edt +from scipy.signal import argrelextrema +from skimage import exposure +from skimage import feature +from skimage import filters +from skimage import measure +from skimage import morphology +from skimage import restoration +from skimage import util +from skimage.future import graph +from skimage.future.graph import RAG + +from model.atom_network import silicon_oxygen_network +############################################################################### +# Utility functions +############################################################################### +from model.saasmi_utils import area_analyser +from model.saasmi_utils.graph import Graph +from utilities.images import numpy_array_drawer + +def add_feature_point_to_rag(rag, labeled_image, feature_markers): + marker_array = np.asarray(feature_markers).T + for label in np.unique(labeled_image): + if label == 0: + continue + x, y = marker_array[label - 1] + rag.nodes[label]['marker'] = (x, y) + + +def skimage_expand_labels(label_image, distance=1): + """Expand labels in label image by ``distance`` pixels without overlapping. + Given a label image, ``expand_labels`` grows label regions (connected components) + outwards by up to ``distance`` pixels without overflowing into neighboring regions. + More specifically, each background pixel that is within Euclidean distance + of <= ``distance`` pixels of a connected component is assigned the label of that + connected component. + Where multiple connected components are within ``distance`` pixels of a background + pixel, the label value of the closest connected component will be assigned (see + Notes for the case of multiple labels at equal distance). + Parameters + ---------- + label_image : ndarray of dtype int + label image + distance : float + Euclidean distance in pixels by which to grow the labels. Default is one. + Returns + ------- + enlarged_labels : ndarray of dtype int + Labeled array, where all connected regions have been enlarged + Notes + ----- + Where labels are spaced more than ``distance`` pixels are apart, this is + equivalent to a morphological dilation with a disc or hyperball of radius ``distance``. + However, in contrast to a morphological dilation, ``expand_labels`` will + not expand a label region into a neighboring region. + This implementation of ``expand_labels`` is derived from CellProfiler [1]_, where + it is known as module "IdentifySecondaryObjects (Distance-N)" [2]_. + There is an important edge case when a pixel has the same distance to + multiple regions, as it is not defined which region expands into that + space. Here, the exact behavior depends on the upstream implementation + of ``scipy.ndimage.distance_transform_edt``. + See Also + -------- + :func:`skimage.measure.label`, :func:`skimage.segmentation.watershed`, :func:`skimage.morphology.dilation` + References + ---------- + .. [1] https://cellprofiler.org + .. [2] https://github.com/CellProfiler/CellProfiler/blob/082930ea95add7b72243a4fa3d39ae5145995e9c/cellprofiler/modules/identifysecondaryobjects.py#L559 + Examples + -------- + >>> labels = np.array([0, 1, 0, 0, 0, 0, 2]) + >>> expand_labels(labels, distance=1) + array([1, 1, 1, 0, 0, 2, 2]) + Labels will not overwrite each other: + >>> expand_labels(labels, distance=3) + array([1, 1, 1, 1, 2, 2, 2]) + In case of ties, behavior is undefined, but currently resolves to the + label closest to ``(0,) * ndim`` in lexicographical order. + >>> labels_tied = np.array([0, 1, 0, 2, 0]) + >>> expand_labels(labels_tied, 1) + array([1, 1, 1, 2, 2]) + >>> labels2d = np.array( + ... [[0, 1, 0, 0], + ... [2, 0, 0, 0], + ... [0, 3, 0, 0]] + ... ) + >>> expand_labels(labels2d, 1) + array([[2, 1, 1, 0], + [2, 2, 0, 0], + [2, 3, 3, 0]]) + """ + + distances, nearest_label_coords = distance_transform_edt( + label_image == 0, return_indices=True + ) + labels_out = np.zeros_like(label_image) + dilate_mask = distances <= distance + # build the coordinates to find nearest labels, + # in contrast to [1] this implementation supports label arrays + # of any dimension + masked_nearest_label_coords = [ + dimension_indices[dilate_mask] + for dimension_indices in nearest_label_coords + ] + nearest_labels = label_image[tuple(masked_nearest_label_coords)] + labels_out[dilate_mask] = nearest_labels + return labels_out + + +def point_to_line_dist(point, line): + """Calculate the distance between a point and a line segment. + + To calculate the closest distance to a line segment, we first need to check + if the point projects onto the line segment. If it does, then we calculate + the orthogonal distance from the point to the line. + If the point does not project to the line segment, we calculate the + distance to both endpoints and take the shortest distance. + + :param point: Numpy array of form [x,y], describing the point. + :type point: numpy.core.multiarray.ndarray + :param line: list of endpoint arrays of form [P1, P2] + :type line: list of numpy.core.multiarray.ndarray + :return: The minimum distance to a point. + :rtype: float + """ + # unit vector + unit_line = line[1] - line[0] + norm_unit_line = unit_line / np.linalg.norm(unit_line) + + # compute the perpendicular distance to the theoretical infinite line + segment_dist = ( + np.linalg.norm(np.cross(line[1] - line[0], line[0] - point)) / + np.linalg.norm(unit_line) + ) + + diff = ( + (norm_unit_line[0] * (point[0] - line[0][0])) + + (norm_unit_line[1] * (point[1] - line[0][1])) + ) + + x_seg = (norm_unit_line[0] * diff) + line[0][0] + y_seg = (norm_unit_line[1] * diff) + line[0][1] + + endpoint_dist = min( + np.linalg.norm(line[0] - point), + np.linalg.norm(line[1] - point) + ) + + # decide if the intersection point falls on the line segment + lp1_x = line[0][0] # line point 1 x + lp1_y = line[0][1] # line point 1 y + lp2_x = line[1][0] # line point 2 x + lp2_y = line[1][1] # line point 2 y + is_betw_x = lp1_x <= x_seg <= lp2_x or lp2_x <= x_seg <= lp1_x + is_betw_y = lp1_y <= y_seg <= lp2_y or lp2_y <= y_seg <= lp1_y + if is_betw_x and is_betw_y: + return segment_dist + else: + # if not, then return the minimum distance to the segment endpoints + return endpoint_dist + + +# Image preparation +def prepare_image(img, selem, **kwargs): + """Preprocessing image.""" + nan_mask = np.isnan(img) + img = np.nan_to_num(img) + img = exposure.rescale_intensity(img) + im_denoised = restoration.denoise_tv_chambolle(img, **kwargs) + im_adapthist = exposure.equalize_adapthist(im_denoised, **kwargs) + im_adapthist[nan_mask] = np.nan + im_eq = filters.rank.equalize(im_adapthist, selem=selem, **kwargs) + return im_denoised, im_adapthist, im_eq + + +############################################################################### +# Markers +############################################################################### +def marking(image, markers=None, **kwargs): + """Return markers and marker list.""" + if markers is None: + row, col = feature.peak_local_max( + util.invert(image), + exclude_border=False, + footprint=morphology.disk(5), + **kwargs + ).T + else: + row, col = markers + row = np.array(row) + col = np.array(col) + markers = np.zeros(image.shape, dtype=np.int) + markers[row.astype(np.int), col.astype(np.int)] = np.arange(len(row)) + 1 + markers = morphology.dilation(markers, morphology.disk(5)) + marker_list = list(zip(row, col)) + return markers, marker_list + + +# Random walker segmentation +def segment(image, markers, marker_list=None, nan_mask=None, disk_size=5, **kwargs): + """Perform Random-Walker-Segmentation.""" + if marker_list: + row, col = np.array(marker_list).T + # Graph of nearest neighbors + + markers = np.zeros(image.shape, dtype=np.int) + markers[row.astype(np.int), col.astype(np.int)] = np.arange(len(row)) + 1 + markers = morphology.dilation(markers, morphology.disk(disk_size)) + labels = skimage_expand_labels(markers, 10) + # labels = segmentation.random_walker(image, markers, mode="cg_mg", **kwargs) + convex_hull = spatial.ConvexHull(marker_list) + convex_hull_points = [marker_list[vertice] for vertice in convex_hull.vertices] + convex_polygon = numpy_array_drawer.get_polygon_mask_array(image.shape, convex_hull_points).astype(bool) + invalid_mask = ~convex_polygon | nan_mask + if invalid_mask is not None: + labels[invalid_mask] = 0 + labels[:, 0] = 0 + labels[0, :] = 0 + labels[-1, :] = 0 + labels[:, -1] = 0 + # labels = morphology.remove_small_objects(labels + return labels + + +def repair_nan_inside_of_image(image): + nan_indicies = np.where(np.isnan(image)) + x_indicies, y_indicies = nan_indicies + for x, y in zip(x_indicies, y_indicies): + try: + left_neighbor = image[x, y - 1] + right_neighbor = image[x, y + 1] + except IndexError: + continue + if not np.isnan(left_neighbor) and not np.isnan(right_neighbor): + image[x, y] = (left_neighbor + right_neighbor) / 2 + return image + + +class SAASMI: + def __init__(self): + self.rag = None # type: graph.RAG + self.labels = None # type: np.array + self.atom_network = None # type: silicon_oxygen_network.SiliconOxygenNetwork + self._area_analyser_dict = {} + + def __plain_add_markers_as_nodes(self, markers): + if markers is None: + return + x_values, y_values = markers + for x, y in zip(x_values, y_values): + self.add_node([x, y], markers=[x, y]) + + def repair_and_prepare_image(self, image, disk_size): + image = image.copy() + image = repair_nan_inside_of_image(image) + self.image_nan_mask = np.isnan(image) + IM = np.nan_to_num(image) + selem = morphology.disk(disk_size) + IM_DENOISED, IM_ADAPTHIST, IM_EQ = prepare_image(IM, selem=selem) + return IM_DENOISED, IM_ADAPTHIST, IM_EQ + + def get_rag_and_labels(self, image, markers, beta=250, force_recreation=False, auto_generate=True, + disk_size=10) -> ( + graph.RAG, np.array): + if self.rag is not None and force_recreation is False: + return self.rag, self.labels + if auto_generate is False: + self.labels = None + self.rag = RAG(self.labels, connectivity=0) + self.__plain_add_markers_as_nodes(markers) + return self.rag, self.labels + BETA = beta + # IM_EQ[image_nan_mask] = np.nan + IM_DENOISED, IM_ADAPTHIST, IM_EQ = self.repair_and_prepare_image(image, disk_size) + MARKERS, MARKER_LIST = marking(IM_ADAPTHIST, markers=markers) + IM_TO_SEGMENT = IM_EQ + self.labels = segment(IM_TO_SEGMENT, MARKERS, MARKER_LIST, nan_mask=self.image_nan_mask, beta=BETA, + disk_size=disk_size) + self.rag = self.create_rag(image, self.labels, markers) + triples = self.get_degree_triples(self.rag, self.labels) + return self.rag, self.labels + + def get_prepared_images(self, image, disk_size): + IM_DENOISED, IM_ADAPTHIST, IM_EQ = self.repair_and_prepare_image(image, disk_size) + return IM_DENOISED, IM_ADAPTHIST, IM_EQ + + def generate_atom_network(self, atom_network_type="silicon_oxygen"): + if atom_network_type == "silicon_oxygen": + self.atom_network = silicon_oxygen_network.SiliconOxygenNetwork(self) + return self.atom_network + + def get_atom_network(self, atom_network_type="silicon_oxygen"): + try: + self.atom_network + except AttributeError: + self.atom_network = None + if self.atom_network is None: + self.generate_atom_network(atom_network_type=atom_network_type) + return self.atom_network + + def remove_all_edges_from_node(self, node): + for edge in self.rag.edges: + if node in edge: + self.rag.remove_edge(*edge) + + def remove_node(self, node_id): + self.remove_all_edges_from_node(node_id) + self.rag.remove_node(node_id) + + def add_node(self, position): + node_id = self.rag.max_id + 1 + self.rag.add_node(node_id, {"marker": position, "centroid": position}) + + def remove_edge(self, key): + self.rag.remove_edge(*key) + + def add_edge(self, key): + distance = self.calc_edge_length(key, self.rag) + angle = self.calc_angle(key, self.rag) + reverse_key = tuple(reversed(key)) + if key not in self.rag.edges and key[0] != key[1]: + if key[0] > key[1]: + key = reverse_key + self.rag.add_edge(*key, distance=distance, angle=angle) + + def calc_angle(self, edge, rag): + try: + start_node_centroid = rag.nodes[edge[0]]['marker'] + end_node_centroid = rag.nodes[edge[1]]['marker'] + except KeyError: + start_node_centroid = rag.nodes[edge[0]]['centroid'] + end_node_centroid = rag.nodes[edge[1]]['centroid'] + x = end_node_centroid[0] - start_node_centroid[0] + y = end_node_centroid[1] - start_node_centroid[1] + angle = math.atan2(y, x) + angle = math.degrees(angle) + if angle < 0: + angle += 180 + return angle + + def add_distance_information_to_edges(self, rag, image): + for edge in rag.edges: + rag.edges[edge]['distance'] = self.calc_edge_length(edge, rag) + rag.edges[edge]['angle'] = self.calc_angle(edge, rag) + + def check_saasmi_edges(self, graph, image, number_of_points=100): + args = [] + for edge in graph.edges: + try: + start_node_centroid = graph.nodes[edge[0]]['marker'] + end_node_centroid = graph.nodes[edge[1]]['marker'] + except KeyError: + start_node_centroid = graph.nodes[edge[0]]['centroid'] + end_node_centroid = graph.nodes[edge[1]]['centroid'] + args.append((edge, start_node_centroid, end_node_centroid, image, number_of_points)) + with multiprocessing.Pool() as p: + edge_gradients = p.starmap(self._get_line_gradient_and_edge, args) + for edge, edge_gradient in edge_gradients: + index_maxima = argrelextrema(edge_gradient, np.greater) + if len(index_maxima[0]) == 1: + graph.edges[edge]['correct_gradient'] = True + else: + graph.edges[edge]['correct_gradient'] = False + + @staticmethod + def _get_line_gradient_and_edge(edge, start_point, end_point, image, number_of_points): + y0, x0 = start_point + y1, x1 = end_point + z = np.nan_to_num(image) + # -- Extract the line... + # Make a line with "num" points... + num = number_of_points + x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num) + + # Extract the values along the line, using cubic interpolation + zi = scipy.ndimage.map_coordinates(z, np.vstack((y, x))) + return edge, zi + + def get_edge_gradient(self, edge, graph, image, number_of_points=100): + try: + start_node_centroid = graph.nodes[edge[0]]['marker'] + end_node_centroid = graph.nodes[edge[1]]['marker'] + except KeyError: + start_node_centroid = graph.nodes[edge[0]]['centroid'] + end_node_centroid = graph.nodes[edge[1]]['centroid'] + _, gradient = self._get_line_gradient_and_edge(edge, start_node_centroid, end_node_centroid, image, + number_of_points) + return gradient + + def calc_edge_length(self, edge, rag): + try: + start_node_centroid = rag.nodes[edge[0]]['marker'] + end_node_centroid = rag.nodes[edge[1]]['marker'] + except KeyError: + start_node_centroid = rag.nodes[edge[0]]['centroid'] + end_node_centroid = rag.nodes[edge[1]]['centroid'] + distance = math.hypot(end_node_centroid[0] - start_node_centroid[0], + end_node_centroid[1] - start_node_centroid[1]) + return distance + + # RAG + def create_rag(self, image, labels, markers): + """Create Region-Adjacency-Graph.""" + rag = graph.RAG(labels) + if rag.has_node(0): + rag.remove_node(0) + add_feature_point_to_rag(rag, labels, feature_markers=markers) + self.add_distance_information_to_edges(rag, image) + return rag + + def _generate_coords(self, relabeld_rag): + coords = {} + for node_id, data in relabeld_rag.nodes(data=True): + try: + coords[node_id] = data["marker"] + except KeyError: + coords[node_id] = data["centroid"] + return coords + + def get_face_cycles_of_length_n(self, rag, labels=None, n=3): + """Return face cycles of given length for given graph.""" + relabeld_rag = nx.relabel_nodes(rag, lambda x: x - 1) + coords = self._generate_coords(relabeld_rag) + helper_graph = Graph() + helper_graph.add_edges_from(relabeld_rag.edges) + helper_graph.embed(coords) + face_cycle_list = [ + face_cycle + for face_cycle in helper_graph.face_cycles() + if len(face_cycle) == n + ] + + # Relabels the nodes so that it starts from 1 + face_cycle_list = list( + map(lambda g: nx.relabel_nodes(g, lambda x: x + 1), face_cycle_list) + ) + + # cleaned_list = [ + # face_cycle + # for face_cycle in face_cycle_list + # if not (set(face_cycle.nodes) & labels) + # ] + cleaned_list = face_cycle_list + return cleaned_list + + def get_degree_triples(self, rag=None, labels=None): + """Return list of degree triples from neighboring nodes.""" + + def degree_tuples(x): + return list(map(lambda dv: tuple(sorted(dict(dv).values())), x)) + + if rag is None or labels is None: + rag = self.rag + labels = self.labels + face_cycle_list = self.get_face_cycles_of_length_n(rag, labels) + degree_triples = degree_tuples( + [rag.degree(face_cycle) for face_cycle in face_cycle_list] + ) + return degree_triples + + def calc_distance(self, point_1, point_2): + distance = math.hypot(point_1[0] - point_2[0], + point_1[1] - point_2[1]) + return distance + + def get_nearest_node_key_and_distance(self, position, graph_type=None): + graph_dict = {"silicon": self.atom_network.silicon_graph, + "oxygen": self.atom_network.oxygen_graph, + "oxygen_silicon": self.atom_network.silicon_oxygen_graph, + } + graph = graph_dict.get(graph_type, self.rag) + + distance = None + key = None + for node in graph: + try: + point = graph.nodes[node]["marker"] + except (IndexError, KeyError): + point = graph.nodes[node]["centroid"] + tmp_distance = self.calc_distance(point, position) + if distance is None or tmp_distance < distance: + key = node + distance = tmp_distance + return key, distance + + def get_nearest_edge_key_and_distance(self, position, graph_type=None): + graph_dict = {"silicon": self.atom_network.silicon_graph, + "oxygen": self.atom_network.oxygen_graph, + "oxygen_silicon": self.atom_network.silicon_oxygen_graph, + } + graph = graph_dict.get(graph_type, self.rag) + distance = None + key = None + + for edge in graph.edges: + try: + start_node_centroid = graph.nodes[edge[0]]['marker'] + end_node_centroid = graph.nodes[edge[1]]['marker'] + except KeyError: + start_node_centroid = graph.nodes[edge[0]]['centroid'] + end_node_centroid = graph.nodes[edge[1]]['centroid'] + tmp_distance = point_to_line_dist(np.array(position[:2]), + [np.array(start_node_centroid), np.array(end_node_centroid)]) + if distance is None or tmp_distance < distance: + key = edge + distance = tmp_distance + return key, distance + + def get_nearest_element(self, position, graph_type): + node_key, node_distance = self.get_nearest_node_key_and_distance(position, graph_type=graph_type) + if node_distance < 1: + return node_key, "node" + edge_key, edge_distance = self.get_nearest_edge_key_and_distance(position, graph_type=graph_type) + if edge_distance is None or node_distance < edge_distance: + return node_key, "node" + else: + return edge_key, "edge" + + def add_area_analyser(self, name="default"): + self.area_analyser_dict[name] = area_analyser.SaasmiAnalyserArea() + + @property + def area_analyser_dict(self): + try: + return self._area_analyser_dict + except AttributeError: + self._area_analyser_dict = {} + return self.area_analyser_dict + + def get_area_analyser_dict(self): + return self.area_analyser_dict + + def get_area_analyser(self, name="default") -> area_analyser.SaasmiAnalyserArea: + try: + return self.area_analyser_dict[name] + except KeyError: + self.add_area_analyser(name) + return self.area_analyser_dict[name] diff --git a/model/saasmi_utils/utils.py b/model/saasmi_utils/utils.py new file mode 100644 index 0000000..b6cac17 --- /dev/null +++ b/model/saasmi_utils/utils.py @@ -0,0 +1,762 @@ +# -*- coding: utf-8 -*- +"""This module provides some utility functions.""" + +import numpy as np +import pandas as pd +from numpy.core.umath_tests import inner1d +from scipy.spatial import ConvexHull + + +def normalize(x, ord=2): + """Normalize a matrix or vector. + + This function is able to return the normalization of a matrix or vector + using one of eight different matrix norms, or one of an infinite number + of vector norms depending on the value of the ``ord`` parameter. + + Parameters + ---------- + x : array_like + Input array must be 1-D or 2-D. + ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional + Order of the norm (see table under ``Notes``). inf means numpy's + `inf` object. + + Returns + ------- + numpy.ndarray + Normalization of the matrix or vector. + + Notes + ----- + For values of ``ord <= 0``, the result is, strictly speaking, not a + mathematical 'norm', but it may still be useful for various numerical + purposes. + The following norms can be calculated: + + ===== ============================ ========================== + ord norm for matrices norm for vectors + ===== ============================ ========================== + None Frobenius norm 2-norm + 'fro' Frobenius norm -- + 'nuc' nuclear norm -- + inf max(sum(abs(x), axis=1)) max(abs(x)) + -inf min(sum(abs(x), axis=1)) min(abs(x)) + 0 -- sum(x != 0) + 1 max(sum(abs(x), axis=0)) as below + -1 min(sum(abs(x), axis=0)) as below + 2 2-norm (largest sing. value) as below + -2 smallest singular value as below + other -- sum(abs(x)**ord)**(1./ord) + ===== ============================ ========================== + + See Also + ----- + :func:`numpy.linalg.norm` + + Examples + -------- + .. testsetup:: + + from saasmi_utils.utils import normalize + + .. doctest:: + + >>> import numpy as np + >>> a = np.array([1, 2, 3]) + >>> a + array([1, 2, 3]) + >>> normalize(a) + array([0.26726124, 0.53452248, 0.80178373]) + >>> normalize(a, np.inf) + array([0.33333333, 0.66666667, 1. ]) + + + >>> b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> b + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + >>> normalize(b) + array([[0.05935386, 0.11870772, 0.17806159], + [0.23741545, 0.29676931, 0.35612317], + [0.41547703, 0.4748309 , 0.53418476]]) + >>> normalize(b, 'fro') + array([[0.05923489, 0.11846978, 0.17770466], + [0.23693955, 0.29617444, 0.35540933], + [0.41464421, 0.4738791 , 0.53311399]]) + + """ + norm = np.linalg.norm(x, ord) + if norm == 0: + norm = 1 + return x / norm + + +def perpendicular_vector(v): + """Return a perpendicular vector to a given one. + + Parameters + ---------- + v : array_like + Input array, must be one-dimensional. + + Returns + ------- + numpy.ndarray + Vector that is perpendicular to `v`. + + Examples + -------- + .. testsetup:: + + from saasmi_utils.utils import perpendicular_vector + + .. doctest:: + + >>> import numpy as np + >>> a = np.array([1, 2, 3]) + >>> a + array([1, 2, 3]) + >>> perpendicular_vector(a) + array([-2, 1, 0]) + + >>> b = np.array([2, 0, 0]) + >>> b + array([2, 0, 0]) + >>> perpendicular_vector(b) + array([0, 1, 0]) + + >>> c = np.array([0, 0, 0]) + >>> c + array([0, 0, 0]) + >>> perpendicular_vector(c) + array([1, 0, 0]) + + """ + v = np.asarray(v) + if v.ndim != 1: + raise ValueError("v must be one-dimensional!") + if v[1] == 0 and v[2] == 0: + if v[0] == 0: + return np.array([1, 0, 0]) + else: + return np.array([0, 1, 0]) + return np.array([-v[1], v[0], 0]) + + +def pca(data, correlation=False, sort=True): + """Apply Principal Component Analysis to the data. + + Parameters + ---------- + data: ndarray + The array containing the data. The array must have NxM dimensions, + where each of the N rows represents a different individual record and + each of the M columns represents a different variable recorded for + that individual record. + correlation : bool, optional + Set the type of matrix to be computed (see Notes): + If True compute the correlation matrix. + If False(Default) compute the covariance matrix. + sort : bool, optional + Set the order that the eigenvalues/vectors will have + If True(Default) they will be sorted (from higher value to less). + If False they won't. + + Returns + ------- + eigenvalues: (1,M) numpy.ndarray + The eigenvalues of the corresponding matrix. + eigenvector: (M,M) numpy.ndarray + The eigenvectors of the corresponding matrix. + + Notes + ----- + The correlation matrix is a better choice when there are different + magnitudes representing the M variables. Use covariance matrix in other + cases. See [#]_. + + References + ---------- + .. [#] https://stackoverflow.com/questions/38754668/plane-fitting-in-a-3d-point-cloud + + """ # noqa + mean = np.mean(data, axis=0) + + data_adjust = data - mean + + #: the data is transposed due to np.cov/corrcoef syntax + if correlation: + + matrix = np.corrcoef(data_adjust.T) + + else: + matrix = np.cov(data_adjust.T) + + eigenvalues, eigenvectors = np.linalg.eig(matrix) + + if sort: + #: sort eigenvalues and eigenvectors + sort = eigenvalues.argsort()[::-1] + eigenvalues = eigenvalues[sort] + eigenvectors = eigenvectors[:, sort] + + return eigenvalues, eigenvectors + + +def best_fitting_plane(points, equation=False): + """Compute the best fitting plane of the given points. + + `points` are the coordinates corresponding to the points from which the + best fitting plane is computed. If `equation` is `True` return the + coefficients of the plane, if `False` return a point of the plane and the + normal vector. + + Parameters + ---------- + points: ndarray + Array with the x, y, z coordinates, must be 2-D. + + equation : bool, optional + Set the oputput plane format. + + Returns + ------- + a, b, c, d : float + The coefficients solving the plane equation :math:`ax + by + cz = d`. + point, normal: numpy.ndarray + The plane defined by a Point and a Normal vector. + + Notes + ----- + See [#]_. + + References + ---------- + .. [#] https://stackoverflow.com/questions/38754668/plane-fitting-in-a-3d-point-cloud + + """ # noqa + _, eig_vectors = pca(points) + #: the normal of the plane is the last eigenvector + normal_vector = eig_vectors[:, 2] + #: get a point from the plane + point = np.mean(points, axis=0) + if equation: + normal_x, normal_y, normal_z = normal_vector + dist = -(np.dot(normal_vector, point)) + return normal_x, normal_y, normal_z, dist + else: + return point, normal_vector + + +def project_to_plane(point, plane): + """Project a given point to a given plane. + + Return the projection of `point` to `plane`, where `plane` is given by a + point on the plane and the normal vector. + + Parameters + ---------- + point: array_like + Input array, must be 1-D. + plane: tuple of two array_like objects + The first array_like object represents a point on the plane, while + the second one represents the normal vector of the plane. + + Returns + ------- + numpy.ndarray + The projection of `point` onto `plane`. + + """ + point = np.asarray(point) + origin, normal_vector = np.asarray(plane) + normal_vector = normalize(normal_vector) + if np.linalg.norm(normal_vector) != 1: + raise ValueError("Vector not normalized!") + + vector = point - origin + dist = np.tensordot(vector, normal_vector, axes=1) + if vector.ndim == 2: + projected_point = point - dist[:, np.newaxis] * normal_vector + else: + projected_point = point - dist * normal_vector + + return projected_point + + +def cross_product_matrix(v): + r"""Return the cross product matrix of a vector. + + This function returns the cross product matrix of the vector `v`. + + Parameters + ---------- + v : array_like + Input array, must be one-dimensional. + + Return + ------ + numpy.ndarray + Cross-product matrix + + Notes + ----- + The vector cross product :math:`v \times w` can be expressed as the + product of a skew-symmetric matrix and the vector :math:`w`, + such that :math:`v \times w = M w`. + See also [#]_. + + References + ---------- + .. [#] https://en.wikipedia.org/wiki/Cross_product#Conversion_to_matrix_multiplication # noqa + + """ + v = np.asarray(v) + if v.ndim != 1: + raise ValueError("v must be one-dimensional!") + matrix = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) + return matrix + + +def rodrigues_matrix(a, b=None, phi=None): + r"""Return the Rodrigues' rotation matrix. + + This function computes Rodrigues' rotation matrix around an axis + and an angle. If `b` is `None` the rotation axis is the unit vector + in direction of `a`, otherwise the rotation axis is the unit vector + in direction of the cross product of `a` and `b`. If `b` is `None`, + `phi` must be given. If `b` is given and `phi` is `None` the + rotation angle is the angle between `a` and `b`. + + Parameters + ---------- + a : array_like + Components of the first vector. + b : array_like, optional + Components of the second vector. + phi: scalar, optional + Angle in radians. + + Returns + ------- + ndarray + Rodrigues' rotation matrix. + + Notes + ----- + The Rodrigues' rotation matrix is an element of the rotation group + :math:`\operatorname{SO}(3)` of :math:`\mathbb R^3`. + It describes the rotation about a rotation axis by an angle + :math:`\varphi`. See also [#]_ + + References + ---------- + .. [#] https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula + + """ + a = np.asarray(a) + if not a.any(): + raise ValueError("zero vector") + + a = normalize(a) + + if b is None: + dtype = a.dtype + if phi is None: + msg = "If b is None, phi mus be specified" + raise ValueError(msg) + else: + b = np.asarray(b) + dtype = np.promote_types(a.dtype, b.dtype) + if not b.any(): + raise ValueError("zero vector") + b = normalize(b) + + if phi is None: + s = np.linalg.norm(np.cross(a, b)) + c = np.dot(a, b) + else: + s = np.sin(phi) + c = np.cos(phi) + + identity = np.identity(3, dtype) + + if c == 1: + return identity + + if b is None: + k = a + else: + if c == -1: + k = normalize(perpendicular_vector(a)) + else: + k = normalize(np.cross(a, b)) + + cross_product = cross_product_matrix(k) + cross_product_squared = np.linalg.matrix_power(cross_product, 2) + + if c == -1: + return identity + 2 * cross_product_squared + + return ( + identity + s * cross_product_matrix + (1 - c) * cross_product_squared + ) + + +def centroid(a): + """Return the cetroid of an array with coordinates. + + This function computes the centroid for the points in `a` and returns + its coordinates. + + Parameters + ---------- + a : array_like, must be 2-D. + List of coordinates. The coordinate tuples are along the zeroth axis. + + Returns + ------- + numpy.ndarray + Coordinates of the centroid computet from `a`. + + """ + a = np.asarray(a) + length = a.shape[0] + dim = a.shape[1] + sum_coordinates = np.zeros(dim) + for i in range(dim): + sum_coordinates[i] = np.sum(a[:, i]) + return sum_coordinates / length + + +def inner_hull(points, level=0, incremental=False, qhull_options=None): + """Return the inner convex hull, of a given level. + + This function returns a `scipy.spatial.ConvexHull` object, computed from + `points` up to a certain `level`, see ``Notes``. + + Parameters + ---------- + points : ndarray of floats, shape (npoints, ndim) + Coordinates of points to construct a convex hull from + level : int + The level of a vertex `v` in points shows after how many steps of + removing all vertices on the boundary of the convex hull of points, + `v` is on the boundary of the convex hull of the remaining points. + incremental : bool, optional + Allow adding new points incrementally. This takes up some additional + resources. + qhull_options : str, optional + Additional options to pass to Qhull. See Qhull manual + for details. (Default: “Qx” for ndim > 4 and “” otherwise) + Option “Qt” is always enabled. + + Returns + ------- + scipy.spatial.ConvexHull + Inner convex hull of given level. + + See Also + -------- + :class:`scipy.spatial.ConvexHull` + + Notes + ----- + The inner convex hull of level zero is just the convex hull. + The inner convex hull of level `n` is the convex hull of points, + that remain after removing the vertices on the boundary of the + inner convex hull of level :math:`n-1`. + + """ + points_cp = np.copy(points) + accumulated_vertices = np.array([], dtype=np.int) + for i in range(level): + hull = ConvexHull(points_cp, qhull_options) + accumulated_vertices = np.append( + accumulated_vertices, hull.vertices, axis=0 + ) + inner_hull_points = np.delete(points_cp, accumulated_vertices, axis=0) + print("inner_hull_points: {}".format(inner_hull_points)) + if len(inner_hull_points) != 0: + # raise ValueError("level {} does not exist.".format(level)) + center = centroid(inner_hull_points) + print("center: {}".format(center)) + points_cp[accumulated_vertices] = center + # print('points: {}'.format(points_cp)) + print("level: {}".format(i + 1)) + + hull = ConvexHull(points_cp, incremental, qhull_options) + + return hull + + +def sort_triple(a): + """Sort 3D points by their first coordinates in a list of triples. + + This function returns an array `b` with the same shape of `a` + with each triple sorted by their first coordinates. + + Parameters + ---------- + a : `(Nx3x3)` array + Input array. + + Returns + ------- + numpy.ndarray + `a` with triples sorted by their first coordinate. + + Examples + -------- + .. testsetup:: + + from saasmi_utils.utils import sort_triple + + .. doctest:: + + >>> import numpy as np + >>> A = np.arange(35, -1, -1).reshape((4, 3, 3)) + >>> A + array([[[35, 34, 33], + [32, 31, 30], + [29, 28, 27]], + + [[26, 25, 24], + [23, 22, 21], + [20, 19, 18]], + + [[17, 16, 15], + [14, 13, 12], + [11, 10, 9]], + + [[ 8, 7, 6], + [ 5, 4, 3], + [ 2, 1, 0]]]) + >>> sort_triple(A) + array([[[29, 28, 27], + [32, 31, 30], + [35, 34, 33]], + + [[20, 19, 18], + [23, 22, 21], + [26, 25, 24]], + + [[11, 10, 9], + [14, 13, 12], + [17, 16, 15]], + + [[ 2, 1, 0], + [ 5, 4, 3], + [ 8, 7, 6]]]) + + """ + sorted_by_x = a[np.arange(a.shape[0])[:, None], np.argsort(a[:, :, 0])] + fst_pts, snd_pts, trd_pts = sorted_by_x.transpose(1, 0, 2) + sorted_by_x_swap23 = np.stack((fst_pts, trd_pts, snd_pts), axis=1) + orientation = np.cross(snd_pts - fst_pts, trd_pts - fst_pts)[:, 2] >= 0 + mask = orientation[:, np.newaxis, np.newaxis] + sorted_by_x = np.where(mask, sorted_by_x, sorted_by_x_swap23) + return sorted_by_x + + +def trilateration(center_points, r1, r2=None, r3=None): + """Determine one intersection of three sphere surfaces. + + Returns one intersection of three sphere surfaces given the centers and + radii of the three spheres. For details about the choice of the + intersection point see ``Notes``. + The three points `a`, `b` and `c` have to be collinear. + + Parameters + ---------- + a : array_like + Components of the first center point + + r1 : scalar + Radius of the first sphere. + + r2 : scalar, optional + Radius of the second sphere. + If any of `r2` or `r3` is None, both are equal to `r1`. + + r3 : scalar, optional + Radius of the third sphere. + If any of `r2` or `r3` is None, both are equal to `r1`. + + Returns + ------- + numpy.ndarray + Intersection of the three spheres. + + Raises + ------ + ValueError + When `a`, `b` and `c` are not collinear. + + Notes + ----- + The intersection point is chosen in positive + direction of the cross product of `(b-a)` and a vector normal to `(b-a)` + intersecting `c`. See also [#]_. + + References + ---------- + .. [#] http://en.wikipedia.org/wiki/Trilateration + + """ + center_points = sort_triple(center_points) + + a, b, c = center_points.transpose(1, 0, 2) + + if any(r is None for r in (r2, r3)): + r2, r3 = (r1, r1) + + ex = (b - a) / (np.linalg.norm(b - a, axis=1))[:, np.newaxis] + # i = np.sum(ex * (c - a), axis=1) + i = inner1d(ex, (c - a)) + ey = (c - a - i[:, np.newaxis] * ex) / ( + np.linalg.norm(c - a - i[:, np.newaxis] * ex, axis=1) + )[:, np.newaxis] + ez = np.cross(ex, ey) + d = np.linalg.norm(b - a, axis=1) + j = inner1d(ey, c - a) + + x = (np.square(r1) - np.square(r2) + np.square(d)) / (2 * d) + y = (np.square(r1) - np.square(r3) + np.square(i) + np.square(j)) / ( + 2 * j + ) - i / j * x + z_square = np.square(r1) - np.square(x) - np.square(y) + mask = z_square < 0 + z_square[mask] *= 0 + z = np.sqrt(z_square) + z[mask] = np.nan + intersection = ( + a + + x[:, np.newaxis] * ex + + y[:, np.newaxis] * ey + + z[:, np.newaxis] * ez + ) + return intersection + + +def polar_coords(point, pole=(0, 0), polar_axis=(1, 0)): + """Return polar coordinates with respect to a pole and a polar axis. + + This function transforms the cartesian coordinates of `point` into + polar coordinates. The polar coordinate system_setup is specified by + `pole` and `polar_axis`. + + Parameters + ---------- + point : array_like + The coordinates of the point to transform to polar coordinates. + + pole : tuple, optional + Set the origin of the polar coordinate system_setup. + The default value is (0,0) + + polar_axis : bool, optional + Set reference direction of the polar coordinate system_setup. + The default value is (1.0) + + Returns + ------- + phi: float + The angle between the polar axis and the ray from the pole + in direction of point. + + radius: float + The distance from the pole to point. + + """ + point = np.asarray(point) + if len(point) != 2: + raise ValueError("2D point required") + pole = np.asarray(pole) + if len(pole) != 2: + raise ValueError("2D point required") + polar_axis = np.asarray(polar_axis) + if len(polar_axis) != 2: + raise ValueError("2D vector required") + + if all(point == pole): + return -np.pi, 0.0 + + if all(polar_axis == pole): + e_str = "Distance between pole and polar_axis must be non-zero!" + raise ValueError(e_str) + + polar_axis_vector = polar_axis - pole + ex = polar_axis_vector / np.linalg.norm(polar_axis_vector) + point_vector = point - pole + x = np.dot(ex, point_vector) + y = ex[0] * point_vector[1] - ex[1] * point_vector[0] + phi = np.arctan2(y, x) + radius = np.linalg.norm(point_vector) + + if phi < 0: + return 2 * np.pi + phi, radius + return phi, radius + + +def project_to_2d(coords, plane=None): + """Project 3-D coordinates into a 2-D plane. + + This function first projects `coords` to `plane`, and then returns + the 2D-coordinates. If `plane` is ``None`` then standard xy-plane + is assumed. + + Parameters + ---------- + plane : tuple of arry_like objects, optional + The first array_like object represents a point on the plane, while + the second one represents the normal vector of the plane. + + Returns + ------- + numpy.ndarray + 2-D coordinates of the projection + + """ + if plane is None: + plane = (np.array([0, 0, 0]), np.array([0, 0, 1])) + origin, normal_vector = np.asarray(plane) + plane_coords = project_to_plane(coords, plane) + plane_coords = plane_coords - origin + rotation_matrix = rodrigues_matrix(normal_vector, (0, 0, 1)) + plane_coords = np.tensordot(rotation_matrix, plane_coords, (1, 1)).T + coords_2d = plane_coords[:, :2] + + return coords_2d + + +def counter_to_dataframe(counter, fst_clmn="labels", snd_clmn="count"): + """Convert a counter object into a dataframe object. + + This function converts :class:`collections.Counter` object into a + :class:`pandas.DataFrame` obeject. + + Parameters + ---------- + counter: collections.Counter + The counter to transform. + fst_clmn: string + The string of the first column of the DataFrame. + snd_clmn: string + The string of the second column of the DataFrame. + + Returns + ------- + pandas.DataFrame + A DataFrame with the objects in the first column and the object counts + in the second. + + """ + df = pd.DataFrame.from_dict(counter, orient="index").reset_index() + sorted_df = df.sort_values("index") + renamed_df = sorted_df.rename( + columns={"index": str(fst_clmn), 0: str(snd_clmn)} + ) + return renamed_df diff --git a/model/visualization/__init__.py b/model/visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/visualization/visualization_methods/__init__.py b/model/visualization/visualization_methods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/visualization/visualization_methods/chart_visualization/__init__.py b/model/visualization/visualization_methods/chart_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/visualization/visualization_methods/chart_visualization/fourier_1d_visualization.py b/model/visualization/visualization_methods/chart_visualization/fourier_1d_visualization.py new file mode 100644 index 0000000..5516380 --- /dev/null +++ b/model/visualization/visualization_methods/chart_visualization/fourier_1d_visualization.py @@ -0,0 +1,12 @@ +import numpy as np + + +class Fourier1D(): + @staticmethod + def get_image(image, image_number=None): + z_image = np.copy(image) + z = z_image[:, 2] + f = np.fft.fft(z) + fshift = np.fft.fftshift(f) + z_image[:, 2] = abs(fshift) + return z_image diff --git a/model/visualization/visualization_methods/chart_visualization/signal_1d_visualization.py b/model/visualization/visualization_methods/chart_visualization/signal_1d_visualization.py new file mode 100644 index 0000000..db3ff7b --- /dev/null +++ b/model/visualization/visualization_methods/chart_visualization/signal_1d_visualization.py @@ -0,0 +1,3 @@ +class Signal1D(): + def get_image(self, image, image_number=None): + return image diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/__init__.py b/model/visualization/visualization_methods/histogram_2d_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/abstract_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/abstract_visualization.py new file mode 100644 index 0000000..e641efa --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/abstract_visualization.py @@ -0,0 +1,10 @@ +from abc import abstractmethod + + +class AbstractVisuzalization: + def get_parameters(self): + return self.__dict__ + + @abstractmethod + def get_image(self, image, image_number): + pass diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/background_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/background_visualization.py new file mode 100644 index 0000000..b3a62c2 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/background_visualization.py @@ -0,0 +1,71 @@ +import cv2 +import numpy as np + +from model.data import scan_organizier +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization +from utilities.images import normalizer + + +class BackgroundVisualization(AbstractVisuzalization): + def __init__(self, scan_index): + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + self.background_subtractor = cv2.createBackgroundSubtractorMOG2(history=len(current_scan.images)) + for image in current_scan.images: + image_8bit = normalizer.generate_greyscale_image(image) + self.background_subtractor.apply(image_8bit, learningRate=0.3) + + def get_image(self, image, image_number): + image_background = normalizer.generate_greyscale_image(image) + fgmask = self.background_subtractor.apply(image_background, learningRate=0) + fgmask = fgmask.astype(np.bool) + image_background[~fgmask] = np.nan + + return image_background + + def __str__(self): + return "background" + + def get_parameters(self): + return self.__dict__ + + +class BackgroundXImagesVisualization(AbstractVisuzalization): + def __init__(self, scan_index): + self.current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + + def get_image(self, image, image_number): + backgroud_subtractor = cv2.createBackgroundSubtractorMOG2() + if image_number > 5: + start_image_number = image_number - 5 + else: + start_image_number = 0 + for iter_image in self.current_scan.images[start_image_number:image_number - 1]: + reference_image = normalizer.generate_greyscale_image(iter_image) + backgroud_subtractor.apply(reference_image) + image_background = normalizer.generate_greyscale_image(image) + fgmask = backgroud_subtractor.apply(image_background) + return_image = image * fgmask + return return_image + + def __str__(self): + return "background" + + def get_parameters(self): + return self.__dict__ + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + img = cv2.imread('459.png', 0) + img = img.astype(float) + bandpass = BackgroundVisualization() + bandpass.min_frequency = 10 + bandpass.max_frequency = 100 + image = bandpass.get_image(img) + plt.subplot(121), plt.imshow(img, cmap='gray') + plt.title('Input'), plt.xticks([]), plt.yticks([]) + plt.subplot(122), plt.imshow(image, cmap='gray') + plt.show() + print("tada") diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/blob_dog_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/blob_dog_visualization.py new file mode 100644 index 0000000..b7e9177 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/blob_dog_visualization.py @@ -0,0 +1,69 @@ +from math import sqrt + +import cv2 +import numpy as np +from skimage.feature import blob_dog + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class BlobDogVisualization(AbstractVisuzalization): + def __init__(self, min_sigma, max_sigma, threshold, overlap): + self.min_sigma = min_sigma + self.max_sigma = max_sigma + self.threshold = threshold + self.overlap = overlap + + def get_image(self, image, image_number=None): + image = image.copy() + image_min = np.nanmin(image) + image_max = np.nanmax(image) + normalize_image = image - image_min + normalize_image = normalize_image / image_max + normalize_image *= 255 + blobs_log = blob_dog(normalize_image, min_sigma=self.min_sigma, max_sigma=self.max_sigma, + threshold=self.threshold, overlap=self.overlap) + h, w = image.shape + blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2) + for blob in blobs_log: + y, x, r = blob + mask = self.__circle_mask(h, w, radius=r, center=[x, y]) + image[mask] = np.nanmax(image) + + return image + + def __circle_mask(cls, h, w, radius=None, center=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = abs(dist_from_center - radius) < 1 + return mask + + def __str__(self): + return "blob_dog" + + def get_parameters(self): + return self.__dict__ + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + img = cv2.imread('459.png', 0) + img = img.astype(float) + bandpass = BlobDogVisualization() + bandpass.min_frequency = 10 + bandpass.max_frequency = 100 + image = bandpass.get_image(img) + plt.subplot(121), plt.imshow(img, cmap='gray') + plt.title('Input'), plt.xticks([]), plt.yticks([]) + plt.subplot(122), plt.imshow(image, cmap='gray') + plt.show() + print("tada") diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/blob_doh_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/blob_doh_visualization.py new file mode 100644 index 0000000..3c7be4a --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/blob_doh_visualization.py @@ -0,0 +1,65 @@ +from math import sqrt + +import cv2 +import numpy as np +from skimage.feature import blob_doh + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class BlobDohVisualization(AbstractVisuzalization): + def __init__(self, min_sigma, max_sigma, threshold, overlap): + self.min_sigma = min_sigma + self.max_sigma = max_sigma + self.threshold = threshold + self.overlap = overlap + + def get_image(self, image, image_number=None): + image = image.copy() + image_min = np.nanmin(image) + image_max = np.nanmax(image) + normalize_image = image - image_min + normalize_image = normalize_image / image_max + blobs_log = blob_doh(normalize_image.astype('double'), min_sigma=self.min_sigma, max_sigma=self.max_sigma, + threshold=self.threshold, overlap=self.overlap) + h, w = image.shape + blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2) + for blob in blobs_log: + y, x, r = blob + mask = self.__circle_mask(h, w, radius=r, center=[x, y]) + image[mask] = np.nanmax(image) + + return image + + def __circle_mask(cls, h, w, radius=None, center=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = abs(dist_from_center - radius) < 1 + return mask + + def __str__(self): + return 'blob_doh' + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + img = cv2.imread('459.png', 0) + img = img.astype(float) + bandpass = BlobDohVisualization() + bandpass.min_frequency = 10 + bandpass.max_frequency = 100 + image = bandpass.get_image(img) + plt.subplot(121), plt.imshow(img, cmap='gray') + plt.title('Input'), plt.xticks([]), plt.yticks([]) + plt.subplot(122), plt.imshow(image, cmap='gray') + plt.show() + print("tada") diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/blob_log_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/blob_log_visualization.py new file mode 100644 index 0000000..0564d31 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/blob_log_visualization.py @@ -0,0 +1,68 @@ +from math import sqrt + +import cv2 +import numpy as np +from skimage.feature import blob_log + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class BlobLogVisualization(AbstractVisuzalization): + def __init__(self, min_sigma, max_sigma, num_sigma, threshold, overlap): + self.min_sigma = min_sigma + self.max_sigma = max_sigma + self.num_sigma = num_sigma + self.threshold = threshold + self.overlap = overlap + + def get_image(self, image, image_number=None): + image = image.copy() + image_min = np.nanmin(image) + image_max = np.nanmax(image) + normalize_image = image - image_min + normalize_image = normalize_image / image_max + normalize_image *= 255 + blobs_log = blob_log(normalize_image, min_sigma=self.min_sigma, max_sigma=self.max_sigma, + num_sigma=self.num_sigma, + threshold=self.threshold, overlap=self.overlap) + h, w = image.shape + blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2) + for blob in blobs_log: + y, x, r = blob + mask = self.__circle_mask(h, w, radius=r, center=[x, y]) + image[mask] = np.nanmax(image) + + return image + + def __circle_mask(cls, h, w, radius=None, center=None): + if center is None: # use the middle of the image + center = (int(w / 2), int(h / 2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w - center[0], h - center[1]) + if radius == 0: + return np.zeros((h, w), dtype=bool) + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2) + + mask = abs(dist_from_center - radius) < 1 + return mask + + def __str__(self): + return 'blob_log' + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + img = cv2.imread('459.png', 0) + img = img.astype(float) + bandpass = BlobLogVisualization() + bandpass.min_frequency = 10 + bandpass.max_frequency = 100 + image = bandpass.get_image(img) + plt.subplot(121), plt.imshow(img, cmap='gray') + plt.title('Input'), plt.xticks([]), plt.yticks([]) + plt.subplot(122), plt.imshow(image, cmap='gray') + plt.show() + print("tada") diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/canny_contours_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/canny_contours_visualization.py new file mode 100644 index 0000000..77fc8c1 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/canny_contours_visualization.py @@ -0,0 +1,24 @@ +import numpy as np +import skimage +import skimage.feature + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class CannyContoursVisualization(AbstractVisuzalization): + def __init__(self, sigma): + self.sigma = sigma + + def get_image(self, image, image_number=None): + return_image = image.copy() + minimum, maximum = np.nanmin(image), np.nanmax(image) + image -= minimum + image = (image / maximum) * 255 + image = np.nan_to_num(image) + sharpend_image = skimage.feature.canny(image, sigma=self.sigma) + return_image[sharpend_image] = maximum + return return_image + + def __str__(self): + return 'canny_contours' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/cross_correlation_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/cross_correlation_visualization.py new file mode 100644 index 0000000..478f305 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/cross_correlation_visualization.py @@ -0,0 +1,59 @@ +import math + +import numpy as np +import scipy.signal +from scipy import interpolate + +from model.data import scan_organizier +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class CrossCorrelation(AbstractVisuzalization): + def __init__(self, reference_image_number, reference_scan_index): + self.reference_image_number = reference_image_number + self.reference_scan_index = reference_scan_index + self.reference_image = scan_organizier.ScanOrganizer.scans[reference_scan_index].images[ + reference_image_number].copy() + self.reference_square = self.prepare_square_image(self.reference_image) + + def get_square_inside_of_circle(self, image): + number_of_bins = image.shape[0] + real_square_length = math.sqrt(number_of_bins ** 2 / 2) + array = image[ + int((number_of_bins - real_square_length) / 2):int( + (number_of_bins + real_square_length) / 2), + int((number_of_bins - real_square_length) / 2):int( + (number_of_bins + real_square_length) / 2)] + return array + + def repair_nans(self, image): + x = np.arange(0, image.shape[1]) + y = np.arange(0, image.shape[0]) + # mask invalid values + array = np.ma.masked_invalid(image) + xx, yy = np.meshgrid(x, y) + # get only the valid values + x1 = xx[~array.mask] + y1 = yy[~array.mask] + newarr = array[~array.mask] + + image = interpolate.griddata((x1, y1), newarr.ravel(), + (xx, yy), + method='cubic') + image_mean = float(np.nanmean(image)) + image = np.nan_to_num(image, nan=image_mean) + return image + + def prepare_square_image(self, image): + image_square = self.get_square_inside_of_circle(image) + image_square_fixed = self.repair_nans(image_square) + return image_square_fixed + + def get_image(self, image, image_number=None): + image_square = self.prepare_square_image(image) + correlation_map = scipy.signal.correlate2d(image_square, self.reference_square, mode='same') + return correlation_map + + def __str__(self): + return 'cross_correlation' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/deviation_map_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/deviation_map_visualization.py new file mode 100644 index 0000000..601b2c1 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/deviation_map_visualization.py @@ -0,0 +1,19 @@ +from model.data import scan_organizier + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class DeviationMapSpectrum(AbstractVisuzalization): + def __init__(self, reference_image_number, reference_scan_index): + self.reference_image_number= reference_image_number + self.reference_scan_index = reference_scan_index + self.reference_image = scan_organizier.ScanOrganizer.scans[reference_scan_index].images[ + reference_image_number].copy() + + def get_image(self, image, image_number=None): + deviation_map = image - self.reference_image + return deviation_map + + def __str__(self): + return 'deviation_map' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/fourier_magnitude_spectrum_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/fourier_magnitude_spectrum_visualization.py new file mode 100644 index 0000000..8eaf55d --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/fourier_magnitude_spectrum_visualization.py @@ -0,0 +1,16 @@ +import numpy as np + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class FourierMagnitudeSpectrum(AbstractVisuzalization): + def get_image(self, image, image_number=None): + image = np.nan_to_num(image) + f = np.fft.fft2(image) + fshift = np.fft.fftshift(f) + magnitude_spectrum = np.abs(fshift) + return magnitude_spectrum + + def __str__(self): + return 'fourier_magnitude' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/fourier_phase_spectrum_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/fourier_phase_spectrum_visualization.py new file mode 100644 index 0000000..5d35b1c --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/fourier_phase_spectrum_visualization.py @@ -0,0 +1,16 @@ +import numpy as np + +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class FourierPhaseSpectrum(AbstractVisuzalization): + def get_image(self, image, image_number=None): + image = np.nan_to_num(image) + f = np.fft.fft2(image) + fshift = np.fft.fftshift(f) + phase_spectrum = np.angle(fshift) + return phase_spectrum + + def __str__(self): + return 'fourier_phase' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/invert_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/invert_visualization.py new file mode 100644 index 0000000..6986acc --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/invert_visualization.py @@ -0,0 +1,13 @@ +from model.filter.filter_2d import invert_image +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import AbstractVisuzalization + + +class InvertImageVisualization(AbstractVisuzalization): + def get_image(self, image, image_number=None): + return_image = image.copy() + inverter = invert_image.InvertImage() + return_image = inverter.get_filtered_image(return_image) + return return_image + + def __str__(self): + return 'image_invert' \ No newline at end of file diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/object_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/object_visualization.py new file mode 100644 index 0000000..6abd1d6 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/object_visualization.py @@ -0,0 +1,35 @@ +import cv2 +import cvlib +import numpy as np + +from model.data import scan_organizier +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization +from utilities.images import normalizer + + +class ObjectVisualization(AbstractVisuzalization): + def __init__(self, scan_index): + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + self.background_subtractor = cv2.createBackgroundSubtractorMOG2(history=len(current_scan.images)) + for image in current_scan.images: + image_8bit = normalizer.generate_greyscale_image(image) + self.background_subtractor.apply(image_8bit, learningRate=0.3) + + def get_image(self, image, image_number): + image_with_objects = normalizer.generate_greyscale_image(image) + image_with_objects = image_with_objects.astype(np.float32) + bbox, label, conf = cvlib.detect_common_objects(image_with_objects) + image_with_objects = image.copy() + for (x, y, x_, y_) in bbox: + cv2.rectangle(image_with_objects, (x, y), + (x_, y_), + (255), 5) + + return image_with_objects + + def __str__(self): + return "background" + + def get_parameters(self): + return self.__dict__ diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/pre_visualization_method_factory.py b/model/visualization/visualization_methods/histogram_2d_visualization/pre_visualization_method_factory.py new file mode 100644 index 0000000..04de824 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/pre_visualization_method_factory.py @@ -0,0 +1,60 @@ +from model.data import scan_organizier +from model.visualization.visualization_methods.histogram_2d_visualization import \ + fourier_magnitude_spectrum_visualization, \ + fourier_phase_spectrum_visualization, deviation_map_visualization, ssim_visualization, canny_contours_visualization, \ + blob_log_visualization, standard_visualization, blob_dog_visualization, \ + blob_doh_visualization, invert_visualization, cross_correlation_visualization, \ + background_visualization + + +class PreVisualizationMethodFactory(): + @classmethod + def generate_pre_visualization_by_type(cls, type, parameters): + if parameters and 'reference_scan' in parameters: + scan_index = scan_organizier.ScanOrganizer.get_scan_index_by_string(parameters['reference_scan']) + + if type == 'image': + return standard_visualization.StandardVisualization() + elif type == 'image_invert': + return invert_visualization.InvertImageVisualization() + elif type == "fourier_magnitude": + return fourier_magnitude_spectrum_visualization.FourierMagnitudeSpectrum() + elif type == "fourier_phase": + return fourier_phase_spectrum_visualization.FourierPhaseSpectrum() + elif type == "deviation_map": + return deviation_map_visualization.DeviationMapSpectrum( + parameters['reference_image'], reference_scan_index=scan_index) + elif type == "ssim_map": + return ssim_visualization.SsimVisualization(parameters['reference_image'], scan_index) + elif type == "canny_contours": + return canny_contours_visualization.CannyContoursVisualization( + parameters['canny_sigma']) + elif type == "blob_log": + return blob_log_visualization.BlobLogVisualization( + min_sigma=parameters['blob_min_sigma'], + max_sigma=parameters['blob_max_sigma'], + num_sigma=parameters['blob_num_sigma'], + threshold=parameters['blob_threshold'], + overlap=parameters['blob_overlap']) + elif type == "blob_dog": + return blob_dog_visualization.BlobDogVisualization( + min_sigma=parameters['blob_min_sigma'], + max_sigma=parameters['blob_max_sigma'], + threshold=parameters['blob_threshold'], + overlap=parameters['blob_overlap']) + + elif type == "blob_doh": + return blob_doh_visualization.BlobDohVisualization( + min_sigma=parameters['blob_min_sigma'], + max_sigma=parameters['blob_max_sigma'], + threshold=parameters['blob_threshold'], + overlap=parameters['blob_overlap']) + elif type == "cross_correlation": + return cross_correlation_visualization.CrossCorrelation( + parameters['reference_image'], reference_scan_index=scan_index) + elif type == "background_subtraction_all_images": + return background_visualization.BackgroundVisualization(scan_index=scan_index) + elif type == "background_subtraction_single_image": + return background_visualization.BackgroundXImagesVisualization(scan_index=scan_index) + else: + return None diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/ssim_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/ssim_visualization.py new file mode 100644 index 0000000..a0234f0 --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/ssim_visualization.py @@ -0,0 +1,21 @@ +import numpy as np +import skimage.metrics + +from model.data import scan_organizier +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class SsimVisualization(AbstractVisuzalization): + def __init__(self, reference_image_number, reference_scan_index): + self.reference_image_number= reference_image_number + self.reference_scan_index = reference_scan_index + self.reference_image = np.nan_to_num(scan_organizier.ScanOrganizer.scans[reference_scan_index].images[reference_image_number]) + + def get_image(self, image, image_number=None): + image = np.nan_to_num(image) + ssim_value, image = skimage.metrics.structural_similarity(self.reference_image, image, full=True) + return image + + def __str__(self): + return 'ssim_map' diff --git a/model/visualization/visualization_methods/histogram_2d_visualization/standard_visualization.py b/model/visualization/visualization_methods/histogram_2d_visualization/standard_visualization.py new file mode 100644 index 0000000..7daf5ae --- /dev/null +++ b/model/visualization/visualization_methods/histogram_2d_visualization/standard_visualization.py @@ -0,0 +1,10 @@ +from model.visualization.visualization_methods.histogram_2d_visualization.abstract_visualization import \ + AbstractVisuzalization + + +class StandardVisualization(AbstractVisuzalization): + def get_image(self, image, image_number=None): + return image + + def __str__(self): + return 'image' diff --git a/model_view/__init__.py b/model_view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model_view/frontend_adapter.py b/model_view/frontend_adapter.py new file mode 100644 index 0000000..4570b22 --- /dev/null +++ b/model_view/frontend_adapter.py @@ -0,0 +1,2311 @@ +import copy +import csv +import logging +import math +import os +import parser +import pickle +import pprint +import sys +from collections import Counter +from datetime import datetime + +import h5py +import imageio +import numpy as np +import yaml +from PyQt5 import QtWidgets, QtCore + +from model import config +from model.atom_network.oxygen_atom import OxygenAtom +from model.atom_network.silicon_atom import SiliconAtom +from model.data import scan_organizier +from model.data.exporter import exporter_rtsstem, exporter_gwyddion, export_scan_zip_file +from model.data.feature.feature_class_concretization import feature_color +from model.data.readers import h5_reader, image_format_reader, sxm_reader, zip_reader, gwy_reader, zip_scan_file_reader, \ + avi_reader, stp_reader, yolo_label_folder_reader, npy_reader, arn_reader +from model.data.scan_representation import scan_file_representation, scan_image_collection, scan_images_grid_data_2d +from model.distortion_project import distortion_drift_correction +from model.drift_detection.cross_correlation_drift_detection import PhaseCrossCorrelationDriftDetection +from model.feature_detector import detector_factory +from model.filter import filter_2d_organizer, filter_1d_organizer, filter_organizer +from model.filter import filter_file_parser +from model.saasmi_utils.saasmi import SAASMI +from model.visualization.visualization_methods.chart_visualization import fourier_1d_visualization, \ + signal_1d_visualization +from model.visualization.visualization_methods.histogram_2d_visualization.pre_visualization_method_factory import \ + PreVisualizationMethodFactory +from model_view.process_control import progress_handler +from utilities.custom_logging import custom_log_handler +from utilities.decorator import progress +from utils import point_utilities +from view import main_window + +log = logging.getLogger(__name__) +ACCEPTED_IMAGE_FILE_FORMATS = ["bmp", "tif", "png", "jpg", "jpeg", ] +ACCEPTED_GWY_FILE_FORMATS = ["gwy"] +scan_organizier.ScanOrganizer.scans = list() +scan_organizier.ScanOrganizer.current_scan = None + +pil_logger = logging.getLogger('PIL') +pil_logger.setLevel(logging.INFO) + + +class FrontEndAdapter: + window = None + + @classmethod + def start_program(cls): + # warnings.filterwarnings("ignore") + QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) + app = QtWidgets.QApplication(sys.argv) + window = main_window.MainWindow(model_view=cls) + window.show() + sys.exit(app.exec_()) + + @classmethod + def set_current_scan(cls, current_scan): + scan_organizier.ScanOrganizer.current_scan = current_scan + + @classmethod + def get_current_scan(cls): + return scan_organizier.ScanOrganizer.current_scan + + @classmethod + @progress.track_process + def load_files(cls, file_paths: list): + if isinstance(file_paths, str): + return cls.load_file(file_paths) + elif len(file_paths) == 1: + return cls.load_file(file_paths[0]) + file_format = file_paths[0].split(".")[-1].lower() + cls.clean_up_file_path_list(file_paths, file_format) + if file_format.lower() == "sxm": + scan = sxm_reader.read_files_to_scan(file_paths) + elif file_format in ACCEPTED_IMAGE_FILE_FORMATS: + scan = image_format_reader.read_files_to_scan(file_paths) + elif file_format == "npy": + scan = npy_reader.read_file_to_scan(file_paths) + else: + accepted_file_formats = ["sxm"] + accepted_file_formats.extend(ACCEPTED_IMAGE_FILE_FORMATS) + log.info( + "Multi file loading not supported for this file format.\nGiven file format: {} \nsupported file formats: {}".format( + file_format, + accepted_file_formats)) + return False + if scan is not None: + scan_organizier.ScanOrganizer.add_scan(scan) + scan_organizier.ScanOrganizer.current_scan = scan + log.info("Files {} were loaded to scan {}".format(file_paths, str(scan))) + return True + else: + return False + + @classmethod + @progress.track_process + def load_file(cls, file_path): + if file_path[-2:].lower() == "h5": + scan = h5_reader.read_file_to_scan(file_path, + number_of_bins=config.config['import_h5']["number_of_bins"], + cut_zoom=config.config['import_h5']["cut_zoom"], + remove_offset=config.config['import_h5']['remove_offset'], + use_first_image_xy_values=config.config["import_h5"][ + "use_first_image_xy_values"], + use_ideal_xy_values=config.config["import_h5"][ + "use_ideal_xy_values"] + ) + elif file_path[-3:].lower() == "vsy": + scan = cls.load_scan(file_path) + elif file_path[-3:].lower() in ACCEPTED_IMAGE_FILE_FORMATS or file_path[ + -4:].lower() in ACCEPTED_IMAGE_FILE_FORMATS: + scan = image_format_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "sxm": + scan = sxm_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "zip": + scan = zip_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() in ACCEPTED_GWY_FILE_FORMATS: + scan = gwy_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "avi": + scan = avi_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "stp": + scan = stp_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "npy": + scan = npy_reader.read_file_to_scan(file_path) + elif file_path[-3:].lower() == "arn": + scan = arn_reader.read_file_to_scan(file_path) + else: + log.warning("The file format of the file {file_path} is not supported".format(file_path=file_path)) + return False + if scan is not None: + scan_organizier.ScanOrganizer.add_scan(scan) + scan_organizier.ScanOrganizer.current_scan = scan + log.info("File {} was loaded to scan {}".format(file_path, str(scan))) + return True + else: + return False + + @staticmethod + def clean_up_file_path_list(file_paths, file_format): + for file_path in file_paths: + if file_paths[0].split(".")[-1].lower() != file_format.lower(): + file_paths.remove(file_path) + return file_paths + + @classmethod + def set_visualization_method(cls, type, parameters, scan_index): + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + current_scan.pre_visualization_parameters = parameters + if parameters is None: + parameters = {} + if 'logarithmic' in parameters and parameters['logarithmic'] is True: + current_scan.visualization_logarithmic = True + else: + current_scan.visualization_logarithmic = False + pre_visualization_method = PreVisualizationMethodFactory.generate_pre_visualization_by_type( + type=type, parameters=parameters) + if pre_visualization_method: + current_scan.pre_visualization_method = pre_visualization_method + + @classmethod + def get_1d_scan_data_range(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + data_range = copy.copy(current_scan.data_range) + return data_range + + @classmethod + def get_images_z_value(cls, image_number, position): + image = cls.get_2d_image_by_number(image_number) + x, y = cls.scale_position_from_frontend_to_backend(position[:2]) + try: + z_value = image[int(round(x)), int(round(y))] + except IndexError: + z_value = float("nan") + return z_value + + @classmethod + def get_2d_image_by_number(cls, number, scan_index=None, fourier_transformed=False, logarithmic=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + image = copy.copy(current_scan.images[number]) + if fourier_transformed: + visualization_instance = PreVisualizationMethodFactory.generate_pre_visualization_by_type( + 'fourier_magnitude', + None) + image = visualization_instance.get_image(image, number) + if logarithmic: + image = 20 * np.log(image + 1) + else: + image = current_scan.pre_visualization_method.get_image(image, number) + if current_scan.visualization_logarithmic: + image -= np.nanmin(image) + image /= np.nanmax(image) + image += 1 + np.seterr(divide='ignore', invalid='ignore') + image = np.where(image != 0, np.log2(image), 0) + np.seterr(divide='warn', invalid='warn') + return image + + @classmethod + def get_step_size(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = cls.get_scan_by_scan_index(scan_index) + return current_scan.step_size + + @classmethod + def set_step_size(cls, x_step_size, y_step_size): + current_scan = scan_organizier.ScanOrganizer.current_scan + try: + current_scan.step_size = [float(x_step_size), float(y_step_size)] + except TypeError: + log.exception() + return False + + @classmethod + def set_step_size_by_formula(cls, formula_x, formula_y): + x_step_size = eval(parser.expr(formula_x).compile()) + y_step_size = eval(parser.expr(formula_y).compile()) + cls.set_step_size(x_step_size, y_step_size) + return True + + @classmethod + def rescale_features_by_formula(cls, formula_x, formula_y): + x_scale = eval(parser.expr(formula_x).compile()) + y_scale = eval(parser.expr(formula_y).compile()) + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + feature_organizer.rescale_features(x_scale, y_scale) + + @classmethod + def get_1d_signal_by_number(cls, number, scan_index=None, fourier_transformed=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + if fourier_transformed: + visualization_1d = fourier_1d_visualization.Fourier1D() + else: + visualization_1d = signal_1d_visualization.Signal1D() + image = current_scan.images_1d[number] + image = visualization_1d.get_image(image) + image_copy = copy.copy(image) + return image_copy + + @classmethod + def get_velocity_array(cls, image_number, ideal, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + return current_scan.get_velocity_array(image_number, ideal) + + @classmethod + def get_angular_velocity_array(cls, image_number, middle_point, point_index, ideal, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + return current_scan.get_angular_velocity_array(image_number, middle_point, point_index, ideal) + + @classmethod + def get_maximum_image_index_by_string(cls, scan_name=None): + if scan_name is None: + return scan_organizier.ScanOrganizer.current_scan.image_maximum_index + else: + scan_index = scan_organizier.ScanOrganizer.get_scan_index_by_string(scan_name) + try: + return scan_organizier.ScanOrganizer.scans[scan_index].image_maximum_index + except TypeError: + return 0 + + @classmethod + def export_image_as_rtsstem_format(cls, file_path, image_number): + exporter = exporter_rtsstem.RtssemExporter(image_number, file_path) + exporter.export() + + @classmethod + def set_nan_color(cls, r, g, b, a): + scan_organizier.ScanOrganizer.current_scan.nan_color = (r / 255, g / 255, b / 255, a) + + @classmethod + def get_nan_color(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = cls.get_scan_by_scan_index(scan_index) + return current_scan.nan_color + + @classmethod + def get_time_stamp_text(cls, image_number, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = cls.get_scan_by_scan_index(scan_index) + number_of_digits = config.config["time_stamps"]["digits_before"] + config.config["time_stamps"][ + "digits_after"] + 1 + number_of_decimals = config.config["time_stamps"]["digits_after"] + try: + time_stamp_to_show = current_scan.time_stamps[image_number] + except TypeError: + return "" + unit = "s" + if config.config["time_stamps"]["unit"] == "minutes": + time_stamp_to_show /= 60 + unit = "m" + elif config.config["time_stamps"]["unit"] == "milliseconds": + time_stamp_to_show *= 10 ** 3 + unit = "ms" + elif config.config["time_stamps"]["unit"] == "microseconds": + time_stamp_to_show *= 10 ** 6 + unit = "µs" + # Substract the start_value + time_stamp_to_show -= config.config["time_stamps"]["start_time"] + time_stamp_text = "{time_stamp:0{number_of_digits}.0{number_of_decimals}f} {unit}".format( + number_of_digits=number_of_digits, + number_of_decimals=number_of_decimals, + time_stamp=time_stamp_to_show, + unit=unit) + return time_stamp_text + + @classmethod + def get_contrast_settings(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = cls.get_scan_by_scan_index(scan_index) + contrast_method = current_scan.contrast_method + contrast_min = current_scan.contrast_min + contrast_max = current_scan.contrast_max + return [contrast_method, contrast_min, contrast_max] + + @classmethod + def add_2d_filter_to_current_scan(cls, filter_index, parameters=None): + if parameters is None: + parameters = list() + scan_organizier.ScanOrganizer.current_scan.add_filter_by_index(filter_index, "2d", parameters=parameters) + + @classmethod + def add_1d_filter_to_current_scan(cls, filter_index, parameters=None, filter_type="1d"): + if parameters is None: + parameters = list() + scan_organizier.ScanOrganizer.current_scan.add_filter_by_index(filter_index, filter_type, parameters=parameters) + + @classmethod + def remove_2d_filter_from_current_scan(cls, filter_index): + scan_organizier.ScanOrganizer.current_scan.remove_filter(filter_index, filter_type="2d") + + @classmethod + def remove_1d_filter_from_current_scan(cls, filter_index, filter_type="1d"): + scan_organizier.ScanOrganizer.current_scan.remove_filter(filter_index, filter_type=filter_type) + + @classmethod + @progress.track_process + def apply_1d_filter_current_scan(cls, filter_type): + if filter_type == "1d_full_signal": + apply_full_signal_filters = True + else: + apply_full_signal_filters = False + scan_organizier.ScanOrganizer.current_scan.apply_1d_filters(apply_full_signal_filters=apply_full_signal_filters) + + @classmethod + @progress.track_process + def apply_2d_filter_current_scan(cls): + scan_organizier.ScanOrganizer.current_scan.apply_2d_filters() + + @classmethod + def generate_filter_logs_file(cls, file_name=None): + scan_organizier.ScanOrganizer.current_scan.export_filter_list_to_text_file(file_name=file_name) + + @classmethod + def get_available_2d_filter_list(cls): + filter_str_list = list() + for filter in filter_2d_organizer.Filter2DOrganizer.filter_classes_available: + filter_str_list.append(str(filter)) + return filter_str_list + + @classmethod + def get_2d_filters_inputs(cls, filter_class_index) -> dict: + return scan_organizier.ScanOrganizer.current_scan.filter_2d_organizer.get_filters_inputs(filter_class_index) + + @classmethod + def get_2d_filter_str_filter_list(cls): + filter_str_list = list() + for filter in scan_organizier.ScanOrganizer.current_scan.filter_2d_organizer.filters: + filter_str_list.append(str(filter)) + return filter_str_list + + @classmethod + def apply_1d_filter_from_test_scan_on_real(cls, test_scan, real_scan, apply_full_signal_filters=False): + real_scan.filter_1d_organizer = test_scan.filter_1d_organizer + real_scan.filter_1d_full_signal_organizer = test_scan.filter_1d_full_signal_organizer + real_scan.apply_1d_filters(apply_full_signal_filters=apply_full_signal_filters) + + @classmethod + def apply_2d_filter_from_test_scan_on_real(cls, test_scan, real_scan): + real_scan.filter_2d_organizer = test_scan.filter_2d_organizer + real_scan.apply_2d_filters() + filter_list = test_scan.filter_2d_organizer.filters + log.info("Filters from the Filter-2D Dialog were applied to the scan\n Filters:\n{}".format( + pprint.pformat(filter_list))) + + @classmethod + def get_available_1d_filter_list(cls): + filter_str_list = list() + for filter in filter_1d_organizer.Filter1DOrganizer.filter_classes_available: + filter_str_list.append(str(filter)) + return filter_str_list + + @classmethod + def get_1d_filters_inputs(cls, filter_class_index) -> dict: + return scan_organizier.ScanOrganizer.current_scan.filter_1d_organizer.get_filters_inputs(filter_class_index) + + @classmethod + def get_1d_filter_str_filter_list(cls, filter_type="1d"): + filter_str_list = list() + current_scan = scan_organizier.ScanOrganizer.current_scan + if filter_type == "1d": + try: + filter_organizer = current_scan.filter_1d_organizer + except AttributeError: + return "" + elif filter_type == "1d_full_signal": + try: + filter_organizer = current_scan.filter_1d_full_signal_organizer + except AttributeError: + return "" + else: + log.info("Something went wrong") + return "" + for filter in filter_organizer.filters: + filter_str_list.append(str(filter)) + + return filter_str_list + + @classmethod + def generate_2d_test_scan_from_scan(cls, scan, image_number, use_visualyze_function=None): + scan_name = scan.file_path.split("/")[-1] + test_scan = scan_file_representation.Scan(scan_name, scan.number_of_bins, scan.grid_2d_window, + frequency_sampling=scan.frequency_sampling) + test_scan_image_collection = {"None": scan_image_collection.ScanGridImageCollection(scan=test_scan, + images_2d=[ + scan.images_2d_original[ + image_number]], + step_size=scan.step_size)} + if use_visualyze_function: + test_scan.pre_visualization_method = scan.pre_visualization_method + test_scan.pre_visualization_parameters = scan.pre_visualization_parameters + test_scan.contrast_method = scan.contrast_method + test_scan.contrast_min = scan.contrast_min + test_scan.contrast_max = scan.contrast_max + test_scan.set_image_collection(test_scan_image_collection) + test_scan.filter_2d_organizer = copy.deepcopy(scan.filter_2d_organizer) + test_scan.apply_2d_filters() + scan_organizier.ScanOrganizer.current_scan = test_scan + return test_scan + + @classmethod + def generate_1d_test_scan_from_scan(cls, scan, image_number, use_visualyze_function=None, filter_type="1d"): + scan_name = scan.file_path.split("/")[-1] + test_scan = scan_file_representation.Scan(scan_name, number_of_bins=scan.number_of_bins, + grid_2d_window=scan.grid_2d_window, + grid_2d_method=scan.grid_2d_method, + frequency_sampling=scan.frequency_sampling) + if filter_type == "1d": + test_scan_image_collection = {"None": scan_image_collection.ScanGridImageCollection(scan=test_scan, + signal_1d_list=[ + scan.image_filtered_1d_full[ + image_number]])} + full_signal = False + elif filter_type == "1d_full_signal": + test_scan_image_collection = {"None": scan_image_collection.ScanGridImageCollection(scan=test_scan, + signal_1d_list= + scan.images_1d_original)} + full_signal = True + else: + log.info("Incorrect Filter Type") + return + if use_visualyze_function: + test_scan.pre_visualization_method = scan.pre_visualization_method + test_scan.pre_visualization_parameters = scan.pre_visualization_parameters + test_scan.contrast_method = scan.contrast_method + test_scan.contrast_min = scan.contrast_min + test_scan.contrast_max = scan.contrast_max + test_scan.set_image_collection(test_scan_image_collection) + test_scan.filter_1d_full_signal_organizer = copy.deepcopy(scan.filter_1d_full_signal_organizer) + test_scan.filter_1d_organizer = copy.deepcopy(scan.filter_1d_organizer) + test_scan.apply_1d_filters(apply_full_signal_filters=full_signal) + scan_organizier.ScanOrganizer.current_scan = test_scan + return test_scan + + @classmethod + def get_freqeuncy_samlping(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + return current_scan.frequency_sampling + + @classmethod + def remove_image_class_from_scan(cls, image_class): + current_scan = scan_organizier.ScanOrganizer.current_scan + current_scan.remove_image_class(image_class) + + @classmethod + def save_scan_in_file(cls, file_path): + with open(file_path, "wb") as file: + pickle.dump(scan_organizier.ScanOrganizer.current_scan, file) + log.info("The Visualyse Object was saved to {}".format(file_path)) + + @classmethod + @progress.track_process + def load_scan(cls, file_path): + with open(file_path, "rb") as file: + scan = pickle.load(file) + return scan + + @classmethod + def change_contrast_for_current_scan(cls, method, contrast_min=None, contrast_max=None): + scan_organizier.ScanOrganizer.current_scan.contrast_method = method + scan_organizier.ScanOrganizer.current_scan.contrast_min = contrast_min + scan_organizier.ScanOrganizer.current_scan.contrast_max = contrast_max + + @classmethod + def get_min_amplitude(cls, image_number): + return scan_organizier.ScanOrganizer.current_scan.get_min_amplitude(image_number) + + @classmethod + def get_max_amplitude(cls, image_number): + return scan_organizier.ScanOrganizer.current_scan.get_max_amplitude(image_number) + + @classmethod + def generate_filter_list_from_log_file(cls, file_path): + filter_file_parser.FilterFileParser.load_from_text_file(file_path) + + @classmethod + def set_show_original_image(cls, show_original_image): + scan_organizier.ScanOrganizer.current_scan.show_original_image = show_original_image + + @classmethod + def get_time_stamp_by_frame(cls, image_number, unit): + time_stamp_to_show = scan_organizier.ScanOrganizer.current_scan.time_stamps[image_number] + if unit == "minutes": + time_stamp_to_show /= 60 + elif unit == "milliseconds": + time_stamp_to_show *= 10 ** 3 + elif unit == "microseconds": + time_stamp_to_show *= 10 ** 6 + return time_stamp_to_show + + @classmethod + def get_current_image_class_string(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + return str(current_scan.current_image_class) + + @classmethod + def get_scan_direction_by_image_number(cls, image_number): + image_class = cls.get_current_image_class_string() + if image_class == "In and Out seperated": + if image_number % 2 == 0: + return "Out" + else: + return "In" + else: + return image_class + + @classmethod + def get_scan_image_information(cls, image_number): + image_id = cls.get_image_id_by_image_number(image_number) + image_information = "Image Information \n" + if image_id is not None: + image_information += "Image Number: {} \nImage ID: {}\n".format(image_number, image_id) + return image_information + + @classmethod + def get_image_id_by_image_number(cls, image_number): + image_id_list = scan_organizier.ScanOrganizer.current_scan.image_id_list + if image_id_list is not None: + return image_id_list[image_number] + else: + return None + + @classmethod + def generate_log_file(cls, log_file_path, additional_information=None): + current_scan = scan_organizier.ScanOrganizer.current_scan + output_log = dict() + output_log['scan'] = {'file_path': current_scan.file_path, + 'number_of_bins': current_scan.number_of_bins, + 'filters': current_scan.get_filters_as_string(), + 'current_image_class': current_scan.current_image_class} + output_log['time_stamps'] = config.config["time_stamps"] + if additional_information == "video": + output_log['video'] = config.config['export_video'] + elif additional_information == 'image': + output_log['image'] = config.config['export_image'] + if config.config['import_h5']['file_path'] == current_scan.file_path: + output_log['import_settings'] = config.config['import_h5'] + if os.path.exists(log_file_path): + os.remove(log_file_path) + yaml.safe_dump(output_log, open(log_file_path, "w"), default_flow_style=False, sort_keys=False) + + @classmethod + def set_z_scale(cls, z_scale): + scan_organizier.ScanOrganizer.current_scan.z_scale = z_scale + + @classmethod + def get_scan_list(cls): + scan_list = list() + for scan in scan_organizier.ScanOrganizer.scans: + scan_list.append(str(scan)) + return scan_list + + @classmethod + @progress.track_process + def get_gwy_inputs(cls, file_path): + return gwy_reader.get_ui_parameters(file_path=file_path) + + @classmethod + def set_config_by_key_sub_key(cls, key, sub_key, value): + config.set_value(key, sub_key, value) + + @classmethod + def get_config_by_key_sub_key(cls, key, sub_key): + return config.get_value(key, sub_key) + + @classmethod + def get_visualization_settings(cls): + visualization_parameters = dict() + current_scan = scan_organizier.ScanOrganizer.current_scan + visualization_parameters['contrast_method'] = current_scan.contrast_method + visualization_parameters['contrast_parameters'] = [current_scan.contrast_min, current_scan.contrast_max] + visualization_parameters['logarithmic'] = current_scan.visualization_logarithmic + visualization_parameters['z_scale'] = current_scan.z_scale + visualization_parameters['background_color'] = current_scan.background_color + visualization_parameters['nan_color'] = current_scan.nan_color + visualization_parameters['visualization_method'] = str(current_scan.pre_visualization_method) + visualization_parameters['visualization_parameters'] = current_scan.pre_visualization_method.get_parameters() + visualization_parameters['current_image_class'] = current_scan.current_image_class + visualization_parameters['image_classes'] = current_scan.image_classes + return visualization_parameters + + @classmethod + def set_current_scan_by_string(cls, scan_name): + for scan in scan_organizier.ScanOrganizer.scans: + if str(scan) == scan_name: + scan_organizier.ScanOrganizer.current_scan = scan + break + + @classmethod + def change_background_color_for_current_scan(cls, r, g, b, a): + current_scan = scan_organizier.ScanOrganizer.current_scan + current_scan.background_color = [r / 255, g / 255, b / 255, a] + + @classmethod + def get_background_color(cls, scan_index): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + return current_scan.background_color + + @classmethod + def set_scan_current_image_class(cls, image_class): + scan_organizier.ScanOrganizer.current_scan.change_current_image_class(image_class) + + @classmethod + @progress.track_process + def export_as_gwy(cls, file_path, frame_number, raw_image_title, filtered_image_title): + if file_path[-4:] != ".gwy": + file_path += ".gwy" + exporter = exporter_gwyddion.GwyExporter(file_path=file_path, frame_number=frame_number, + raw_title=raw_image_title, filtered_title=filtered_image_title) + exporter.export() + + @classmethod + def set_feature_class_visualization_representation(cls, feature_class_name, representation): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string(feature_class_name) + feature_class.visualization_representation = representation + + @classmethod + def get_feature_class_visualization_representation(cls, feature_class_name): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string(feature_class_name) + try: + return feature_class.visualization_representation + except AttributeError: + return None + + @classmethod + def load_yolo_label_directory(cls, directory_path): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + yolo_label_folder_reader.read_features_from_folder(directory_path, feature_organizer) + + @classmethod + def set_progress_representation(cls, progress_representation): + progress_handler.default_progress_handler.progress_representation = progress_representation + + @classmethod + def add_feature_to_feature_class(cls, feature_class_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.add_feature(feature_class_index=feature_class_index) + + @classmethod + def rename_feature_class(cls, old_feature_class_name, new_feature_class_name): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string(old_feature_class_name) + feature_class.feature_class_name = new_feature_class_name + + @classmethod + def add_feature_class(cls, feature_class, color): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.add_feature_class(feature_class, color) + feature_class_index = len(feature_organizer.feature_classes) - 1 + cls.save_feature_tracker_in_file() + return feature_class_index + + @classmethod + def add_point_to_feature(cls, feature_class_index, feature_index, position, image_number, + maximum_number_of_points=0): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + position = cls.scale_position_from_frontend_to_backend(position=position) + point_added = feature_organizer.add_point_to_feature(feature_class_index=feature_class_index, + feature_index=feature_index, + point=position, image_number=image_number, + maximum_number_of_points=maximum_number_of_points) + cls.save_feature_tracker_in_file() + return point_added + + @classmethod + def remove_nearest_feature_point(cls, position, image_number): + position = cls.scale_position_from_frontend_to_backend(position) + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + return feature_organizer.remove_nearest_point(ref_position=position, image_number=image_number) + + @classmethod + def get_feature_classes_list(cls): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_class_string_list = [str(feature_class) for feature_class in feature_organizer.feature_classes] + return feature_class_string_list + + @classmethod + def get_features_by_feature_class_index(cls, feature_class_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_list = [str(i) for i in + range(len(feature_organizer.get_features(feature_class_index=feature_class_index)))] + return feature_list + + @classmethod + def scale_position_from_backend_to_frontend(cls, position): + current_scan = scan_organizier.ScanOrganizer.current_scan + step_size_col, step_size_row = current_scan.step_size + position = [position[0] * step_size_col, position[1] * step_size_row, *position[2:]] + return position + + @classmethod + def scale_position_from_frontend_to_backend(cls, position): + current_scan = scan_organizier.ScanOrganizer.current_scan + step_size_col, step_size_row = current_scan.step_size + position = [position[0] / step_size_col, position[1] / step_size_row, *position[2:]] + return position + + @classmethod + def get_feature_points(cls, feature_class_index, feature_index, image_number): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_points = feature_organizer.get_feature_points_by_image_number(feature_class_index=feature_class_index, + feature_index=feature_index, + image_number=image_number) + + scaled_feature_points = list() + for point in feature_points: + scaled_feature_points.append(cls.scale_position_from_backend_to_frontend(position=point)) + return scaled_feature_points + + @classmethod + def remove_all_feature_points_from_image(cls, feature_class_index, image_number): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.remove_all_points_from_image(feature_class_index, image_number) + + @classmethod + def remove_feature_class(cls, feature_class_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.remove_feature_class(feature_class_index=feature_class_index) + + @classmethod + def remove_feature(cls, feature_class_index, feature_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.remove_feature_from_feature_class(feature_class_index=feature_class_index, + feature_index=feature_index) + + @classmethod + def remove_feature_point(cls, feature_class_index, feature_index, point_index, image_number): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.remove_feature_point(feature_class_index=feature_class_index, + feature_index=feature_index, point_index=point_index, + image_number=image_number) + + @classmethod + def get_feature_dictionary(cls, image_number, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = cls.get_scan_by_scan_index(scan_index) + feature_organizer = current_scan.feature_organizer_instance + feature_dict = feature_organizer.get_feature_dict(image_number) + for feature_class_name, feature_class in feature_dict.items(): + for feature_index, feature in feature_class.items(): + for point_id, point in enumerate(feature): + feature[point_id] = cls.scale_position_from_backend_to_frontend(point) + return feature_dict + + @classmethod + def auto_fill_feature_tracker(cls, current_image): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.auto_fill_images(current_image) + + @classmethod + def save_feature_tracker_in_file(cls, file_path=None): + if file_path is None: + file_path = "tmp/feature_tracker" + try: + os.mkdir("tmp") + except OSError: + if not os.path.isdir("tmp"): + raise + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.save_in_file(file_path) + + @classmethod + @progress.track_process + def validate_feature_tracker_file(cls, file_path): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + reason_not_to_load = feature_organizer.validate_file_to_load(file_path) + return reason_not_to_load + + @classmethod + @progress.track_process + def load_feature_tracker_from_file(cls, file_path, feature_ratio): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.load_features_from_file(file_path, feature_ratio) + + @classmethod + def get_scan_by_scan_index(cls, scan_index): + if scan_index is None: + scan = scan_organizier.ScanOrganizer.current_scan + else: + scan = scan_organizier.ScanOrganizer.scans[scan_index] + return scan + + @classmethod + def get_maximum_image_index(cls, scan_index=None): + if scan_index is None: + scan = scan_organizier.ScanOrganizer.current_scan + else: + scan = scan_organizier.ScanOrganizer.scans[scan_index] + return len(scan.images) - 1 + + @classmethod + def save_current_scan_in_zip_file(cls, file_path): + export_scan_zip_file.save_current_scan_in_zip_file(cls, file_path) + + @classmethod + @progress.track_process + def load_from_scan_zip_file(cls, file_path): + scan = zip_scan_file_reader.read_zip_scan_file(file_path=file_path) + if scan is None: + return False + scan_organizier.ScanOrganizer.add_scan(scan) + scan_organizier.ScanOrganizer.current_scan = scan + return True + + @classmethod + def get_feature_class_color_by_name(cls, feature_class_name, scan_index=None): + if scan_index is None: + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + else: + feature_organizer = scan_organizier.ScanOrganizer.scans[scan_index].feature_organizer_instance + color = feature_organizer.get_color_by_feature_class_name(feature_class_name) + return color + + @classmethod + def set_feature_class_color(cls, feature_class_name, color): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_organizer.set_color_by_feature_class_name(feature_class_name, color) + + @classmethod + def get_time_stamps(cls): + time_stamps = copy.copy(scan_organizier.ScanOrganizer.current_scan.time_stamps) + return time_stamps + + @classmethod + def get_time_stamp_delta(cls, image_number=None): + time_stamps = scan_organizier.ScanOrganizer.current_scan.time_stamps + if len(time_stamps) < 2: + return None + if image_number is None: + try: + return time_stamps[1] - \ + time_stamps[0] + except IndexError: + return None + else: + try: + return time_stamps[image_number + 1] - \ + time_stamps[image_number] + except IndexError: + return time_stamps[image_number] - \ + time_stamps[image_number - 1] + + @classmethod + def get_feature_jump_dict(cls, selected_feature_classes, selected_feature_dict): + jump_dict = {} + for feature_class_i, feature_class_name in enumerate(selected_feature_classes): + feature_list = selected_feature_dict[feature_class_name] + jump_dict[feature_class_name] = {} + for feature_index in feature_list: + jump_positions = cls.get_feature_jump_position_list(feature_class_name=feature_class_name, + feature_index=feature_index) + jump_dict[feature_class_name][feature_index] = jump_positions + return jump_dict + + @classmethod + def get_nearest_jump_information(cls, selected_feature_classes, selected_feature_dict, position): + jump_dict = cls.get_feature_jump_dict(selected_feature_classes, selected_feature_dict) + min_distance = float("inf") + nearest_jump_information = {} + for feature_class_name, features_jump_list in jump_dict.items(): + for feature_index, feature_jump_list in features_jump_list.items(): + for jump_index in range(len(feature_jump_list)): + try: + jump_start = feature_jump_list[jump_index] + jump_end = feature_jump_list[jump_index + 1] + jump_x = (jump_end[0] - jump_start[0]) / 2 + jump_start[0] + jump_y = (jump_end[1] - jump_start[1]) / 2 + jump_start[1] + distance_square = (position[0] - jump_x) ** 2 + (position[1] - jump_y) ** 2 + if distance_square < min_distance: + min_distance = distance_square + nearest_jump_information = {"feature_class_name": feature_class_name, + "feature_index": feature_index, + "image_number": jump_index, + "jump_start": jump_start, + "jump_end": jump_end} + except IndexError: + pass + return nearest_jump_information + + @classmethod + def get_jump_angle_list(cls, image_range, feature_class_index, feature_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_analyser = feature_organizer.feature_analyser + jump_angle_list = feature_analyser.get_jump_angle_list(image_range, feature_class_index, feature_index) + return jump_angle_list + + @classmethod + def get_jump_informations(cls, image_range, selected_feature_classes, selected_feature_dict, threshold, + count_nan_jumps): + time_stamps = scan_organizier.ScanOrganizer.current_scan.time_stamps + time_delta = time_stamps[1] - time_stamps[0] + jump_frequency = 0 + jump_frequency_normed = 0 + number_of_jumps = 0 + for selected_feature_class_name in selected_feature_classes: + feature_list = selected_feature_dict[selected_feature_class_name] + feature_class_jump_frequency_dict, feature_class_jump_frequency_dict_normed, feature_class_number_of_jumps = cls.get_feature_class_jump_per_image_and_number_of_jumps( + image_range, selected_feature_class_name, feature_list, threshold=threshold, + count_nan_jumps=count_nan_jumps) + for feature_index, jump_frequency_per_image in feature_class_jump_frequency_dict.items(): + jump_frequency += jump_frequency_per_image / time_delta + jump_frequency_normed += feature_class_jump_frequency_dict_normed[feature_index] / time_delta + number_of_jumps += feature_class_number_of_jumps[feature_index] + return jump_frequency, jump_frequency_normed, number_of_jumps + + @classmethod + def get_feature_class_movement_distance(cls, image_range, feature_class_name, feature_list=None): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_analyser = feature_organizer.feature_analyser + feature_class_movement_vectors = feature_analyser.get_feature_class_movement_vector(image_range, + feature_class_name, + feature_list) + feature_class_movement_distances = [] + for feature_movement_vectors in feature_class_movement_vectors: + for movement_vector in feature_movement_vectors: + if movement_vector != (0, 0) and not math.isnan(movement_vector[0]): + movement_vector = cls.scale_position_from_backend_to_frontend(movement_vector) + distance = math.sqrt( + movement_vector[0] ** 2 + movement_vector[1] ** 2) + feature_class_movement_distances.append(distance) + return feature_class_movement_distances + + @classmethod + def get_average_jump_distance(cls, image_range, selected_feature_classes, selected_feature_dict, threshold): + sum_jump_distance = 0 + number_of_jumps = 0 + for feature_class_name in selected_feature_classes: + feature_list = selected_feature_dict[feature_class_name] + feature_class_movement_distances = cls.get_feature_class_movement_distance(image_range, feature_class_name, + feature_list) + for jump_distance in feature_class_movement_distances: + if jump_distance >= threshold: + sum_jump_distance += jump_distance + number_of_jumps += 1 + if number_of_jumps > 0: + average_jump_distance = sum_jump_distance / number_of_jumps + else: + average_jump_distance = 0 + return average_jump_distance + + @classmethod + def get_feature_class_jump_per_image_and_number_of_jumps(cls, image_range, feature_class_name, feature_list, + threshold, + count_nan_jumps=True): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_analyser = feature_organizer.feature_analyser + step_size = cls.get_step_size() + threshold_backend = threshold / step_size[0] + feature_class_jump_dict = feature_analyser.get_feature_class_jump_dict(image_range, feature_class_name, + feature_list, + threshold=threshold_backend, + count_nan_jumps=count_nan_jumps) + feature_class_duration_dict = feature_analyser.get_feature_class_duration_dict(image_range, feature_class_name, + feature_list) + feature_class_frequency = {} + feature_class_frequency_normed = {} + feature_class_number_of_jumps = {} + for feature_index, jump_list in feature_class_jump_dict.items(): + number_of_jumps = sum(jump_list) + feature_duration = feature_class_duration_dict[feature_index] + if feature_duration != 0: + jumps_per_image_normed = number_of_jumps / feature_duration + else: + jumps_per_image_normed = 0 + jumps_per_image = number_of_jumps / (image_range[1] - image_range[0]) + feature_class_frequency_normed[feature_index] = jumps_per_image_normed + feature_class_frequency[feature_index] = jumps_per_image + feature_class_number_of_jumps[feature_index] = number_of_jumps + return feature_class_frequency, feature_class_frequency_normed, feature_class_number_of_jumps + + @classmethod + def get_feature_jump_position_list(cls, feature_class_name, feature_index): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_analyser = feature_organizer.feature_analyser + + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string(feature_class_name) + feature_centroid_list = feature_analyser.get_centroid_list(feature_class_index, feature_index) + feature_points_scaled = [] + for feature_centroid in feature_centroid_list: + feature_points_scaled.append(cls.scale_position_from_backend_to_frontend(feature_centroid)) + return feature_points_scaled + + @classmethod + @progress.track_process + def auto_detect_features_in_image_and_add_to_feature_class(cls, feature_class_index, image_number, + detector_name=None): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + parameters = config.get_value("feature_settings", "feature_detector_parameters") + image = scan_organizier.ScanOrganizer.current_scan.images[image_number] + feature_detector = detector_factory.get_feature_detector(detector_name) + feature_list = feature_detector.get_feature_list(image, parameters) + if feature_list is None: + return + for index, feature_point in enumerate(feature_list): + feature_organizer.auto_add_feature_points(feature_class_index=feature_class_index, + image_number=image_number, + point=feature_point) + + @classmethod + def auto_detect_features_in_image(cls, image, parameters, detector_name=None): + feature_detector = detector_factory.get_feature_detector(detector_name) + feature_list = feature_detector.get_feature_list(image, parameters) + for index, feature in enumerate(feature_list): + feature_list[index] = cls.scale_position_from_backend_to_frontend(feature) + return feature_list + + @classmethod + @progress.track_process + def auto_detect_features_in_all_images(cls, feature_class_index): + for image_number in range(len(scan_organizier.ScanOrganizer.current_scan.images)): + cls.auto_detect_features_in_image_and_add_to_feature_class(feature_class_index=feature_class_index, + image_number=image_number) + + @classmethod + @progress.track_process + def correct_drift_by_drift_feature_class(cls): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + feature_analyser = feature_organizer.feature_analyser + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string("Drift_Correction") + if feature_class is not None: + drift_vector = feature_analyser.get_feature_class_drift_vector(feature_class_index=feature_class_index) + else: + log.info("No Drift_Correction Feature Class") + return None + number_of_bins = scan_organizier.ScanOrganizer.current_scan.number_of_bins + scan_organizier.ScanOrganizer.current_scan.apply_drift_correction_vectors(drift_vector, number_of_bins) + cls.remove_feature_class(feature_class_index=feature_class_index) + try: + scan_organizier.ScanOrganizer.current_scan.apply_1d_filters() + except AttributeError: + pass + scan_organizier.ScanOrganizer.current_scan.apply_2d_filters() + feature_organizer.remove_feature_class(feature_class_name="Drift_Correction") + log.info("Drift correction was done by feature") + + @classmethod + @progress.track_process + def correct_drift_by_phase_cross_correlation(cls, method, image_offset_list, maximum_vector_length=0): + drift_detector = PhaseCrossCorrelationDriftDetection() + image_list = copy.copy(scan_organizier.ScanOrganizer.current_scan.images) + parameter = {"image_offset_list": image_offset_list, + "method": method, + "maximum_vector_length": maximum_vector_length} + drift_vector = drift_detector.get_drift_vector(image_list, parameter) + number_of_bins = scan_organizier.ScanOrganizer.current_scan.number_of_bins + scan_organizier.ScanOrganizer.current_scan.apply_drift_correction_vectors(drift_vector, + number_of_bins=number_of_bins) + scan_organizier.ScanOrganizer.current_scan.apply_2d_filters() + + @classmethod + def get_color_by_name(cls, name): + return feature_color.get_feature_class_color_by_name(name) + + @classmethod + def get_filter_mask(cls, filter_class_index: int, parameters: list): + image = cls.get_2d_image_by_number(0) + parameters.insert(0, image) + return scan_organizier.ScanOrganizer.current_scan.filter_2d_organizer.get_filter_mask(filter_class_index, + parameters) + + @classmethod + def remove_scan(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + scan_organizier.ScanOrganizer.scans.remove(current_scan) + if len(scan_organizier.ScanOrganizer.scans) == 0: + scan_organizier.ScanOrganizer.current_scan = None + else: + scan_organizier.ScanOrganizer.current_scan = scan_organizier.ScanOrganizer.scans[-1] + + @classmethod + def get_feature_detection_function_list(cls): + return detector_factory.get_detectors_str_list() + + @classmethod + def get_saasmi_rag_and_label(cls, image_number, beta=250, scan_index=None, force_recreation=False, + auto_generate=True, disk_size=10): + if scan_index is not None: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + else: + current_scan = scan_organizier.ScanOrganizer.current_scan + markers = current_scan.feature_organizer_instance.get_markers(image_number) + if markers is None and auto_generate is True: + cls.auto_detect_features_in_image_and_add_to_feature_class(0, image_number) + markers = current_scan.feature_organizer_instance.get_markers(image_number) + saasmi = current_scan.get_saasmi(image_number=image_number) + image = cls.get_2d_image_by_number(number=image_number) + rag, labels = saasmi.get_rag_and_labels(image=image, markers=markers, beta=beta, + force_recreation=force_recreation, auto_generate=auto_generate, + disk_size=disk_size) + return rag, labels + + @classmethod + def get_saasmi_prepared_images(cls, image_number, disk_size): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + image = cls.get_2d_image_by_number(number=image_number) + im_denoised, im_adapthist, im_eq = saasmi.get_prepared_images(image, disk_size) + return im_denoised, im_adapthist, im_eq + + @classmethod + def get_saasmi_node_information(cls, image_number, element_key, graph_type=None): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + atom_network = saasmi.get_atom_network() + if graph_type == "silicon": + graph = atom_network.silicon_graph + elif graph_type == "oxygen": + graph = atom_network.oxygen_graph + elif graph_type == "oxygen_silicon": + graph = atom_network.silicon_oxygen_graph + else: + graph = saasmi.rag + node = graph.nodes[element_key] + node_information = [] + node_information_dict = {**node} + node_information_dict["degree"] = graph.degree[element_key] + edge_list = [] + for edge in graph.edges: + if element_key in edge: + edge_list.append(edge) + edge_str_list = [str(edge) for edge in edge_list] + node_information_dict["edges"] = edge_list + node_information.append("Edges \n {edge_list}".format(edge_list="\n".join(edge_str_list))) + return node_information_dict + + @classmethod + def get_saasmi_edge_information(cls, image_number, key, graph_type): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + atom_network = saasmi.get_atom_network() + if graph_type == "silicon": + graph = atom_network.silicon_graph + elif graph_type == "oxygen": + graph = atom_network.oxygen_graph + elif graph_type == "oxygen_silicon": + graph = atom_network.silicon_oxygen_graph + else: + graph = saasmi.rag + try: + edge = graph.edges[key] + except (KeyError, TypeError): + return None + edge_information_dict = {"distance": edge["distance"], + "angle": edge["angle"], } + try: + correct_gradient = edge['correct_gradient'] + edge_information_dict['correct_gradient'] = correct_gradient + except KeyError: + pass + return edge_information_dict + + @classmethod + def remove_saasmi_rag_edge(cls, image_number, key, graph_type): + graph = cls.get_graph_by_graph_type(graph_type, image_number) + try: + graph.remove_edge(*key) + except Exception as e: + log.exception() + + @classmethod + def add_saasmi_rag_edge(cls, image_number, key, scan_index=None): + if scan_index is not None: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + else: + current_scan = scan_organizier.ScanOrganizer.current_scan + current_scan.saasmi_rag_add_edge(image_number, key) + + @classmethod + def export_saasmi_as_xyz_file(cls, image_number, graph_type, file_path, comment_line, stretch_factor, + apply_image_mask=False): + try: + file_path, extention = file_path.split(".") + except ValueError: + pass + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + file_path += ".xyz" + graph = cls.get_graph_by_graph_type(graph_type, image_number) + + if apply_image_mask is True: + image_mask = cls.get_analyse_area_mask_image(image_number) + analyser = saasmi.get_area_analyser() + graph = analyser.get_subgraph_inside_image_mask(graph=graph, polygon_image_mask=image_mask) + xyz_file_str = "" + xyz_file_str += str(len(graph.nodes)) + "\n" + comment_line = "{}\n".format(comment_line) + xyz_file_str += comment_line + for node in graph.nodes: + node_instance = graph.nodes[node] + node_position = node_instance["marker"] + node_position_scaled = cls.scale_position_from_backend_to_frontend(node_position) + x, y = node_position_scaled + x *= stretch_factor[0] + y *= stretch_factor[1] + z = 0.0 + node_atom_type = node_instance["atom_type"] + if node_atom_type == "silicon": + symbol = "Si" + else: + symbol = "O" + xyz_file_str += "{}\t {}\t {}\t{} \n".format(symbol, x, y, z) + with open(file_path, "w") as xyz_file: + xyz_file.write(xyz_file_str) + + @classmethod + @progress.track_process + def drift_correction_by_ransac(cls, reference_image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + image_reference = current_scan.images[reference_image_number] + corrected_image_list = list() + for i in range(len(current_scan.images)): + image_to_correct = current_scan.images[i].copy() + if i == reference_image_number: + corrected_image_list.append(image_to_correct) + continue + try: + transform = distortion_drift_correction.gen_warp_transform(image_to_correct, image_reference, + progress=0, + debug=0, + tftype='polynomial', islog=True) + corrected_image = distortion_drift_correction.apply_warp_transform(image_to_correct, transform, + progress=0, + debug=0) + except Exception as e: + log.exception("message") + log.info("Drift correction could not be applied on image {}".format(i)) + continue + corrected_image_list.append(corrected_image) + time_stamps = current_scan.time_stamps + step_size = current_scan.step_size + ransac_image_collection = scan_image_collection.ScanGridImageCollection(scan=current_scan, + images_2d=corrected_image_list, + time_stamps=time_stamps, + step_size=step_size) + ransac_image_class_name = current_scan.current_image_class + '_ransac_corrected' + current_scan.scan_image_collection_dict[ransac_image_class_name] = ransac_image_collection + log.info("Drift was corrected by RANSAC") + return ransac_image_class_name + + @classmethod + def get_image_classes(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + return current_scan.scan_image_collection_dict.keys() + + @classmethod + def add_custom_logging_function(cls, callback_function): + program_logger = logging.getLogger() + program_logger.addHandler(custom_log_handler.CustomLogHandler(callback_function=callback_function)) + + @classmethod + def get_availible_to_grid_methods(cls): + return scan_images_grid_data_2d.TO_GRID_METHOD + + @classmethod + @progress.track_process + def get_full_1d_signal_fourier_transformed(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + full_1d_signal = current_scan.get_full_1d_signal() + full_1d_signal_fourier = fourier_1d_visualization.Fourier1D.get_image(full_1d_signal) + return full_1d_signal_fourier + + @classmethod + @progress.track_process + def get_full_1d_signal(cls, scan_index=None): + if scan_index is None: + current_scan = scan_organizier.ScanOrganizer.current_scan + else: + current_scan = scan_organizier.ScanOrganizer.scans[scan_index] + full_1d_signal = current_scan.get_full_1d_signal() + return full_1d_signal + + @classmethod + def add_point_to_feature_for_all_images(cls, feature_class_index, feature_index, position): + current_scan = scan_organizier.ScanOrganizer.current_scan + number_of_images = len(current_scan.images) + for i in range(number_of_images): + cls.add_point_to_feature(feature_class_index, feature_index, position, i) + + @classmethod + def auto_fill_selected_features(cls, current_image, feature_class_dict): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + for feature_class_name, feature_list in feature_class_dict.items(): + feature_organizer.auto_fill_images_with_selected_features(current_image, feature_class_name, feature_list) + + @classmethod + def get_nearest_point(cls, position, image_number): + position = cls.scale_position_from_frontend_to_backend(position) + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + minimum_distance, feature_class_index, feature_index, point_index, position = feature_organizer.get_nearest_point( + ref_position=position, image_number=image_number) + return feature_class_index, feature_index, point_index + + @classmethod + def saasmi_remove_node(cls, image_number, node_id, graph_type=None): + graph = cls.get_graph_by_graph_type(graph_type, image_number) + if graph_type is None or graph_type == "rag": + try: + node_positon = graph.nodes[node_id]['marker'] + except KeyError: + node_positon = graph.nodes[node_id]['centroid'] + current_scan = scan_organizier.ScanOrganizer.current_scan + node_positon = cls.scale_position_from_frontend_to_backend(node_positon) + cls.remove_nearest_feature_point(position=node_positon, image_number=image_number) + elif graph_type == "oxygen_silicon": + try: + oxygen_graph = cls.get_graph_by_graph_type(image_number=image_number, graph_type="oxygen") + oxygen_graph.remove_node(node_id) + except: + silicon_graph = cls.get_graph_by_graph_type(image_number=image_number, graph_type="silicon") + silicon_graph.remove_node(node_id) + else: + silicon_oxygen_graph = cls.get_graph_by_graph_type(image_number=image_number, graph_type="oxygen_silicon") + silicon_oxygen_graph.remove_node(node_id) + graph.remove_node(node_id) + + @classmethod + def saasmi_add_node(cls, saasmi_position, feature_class_index, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + cls.add_feature_to_feature_class(feature_class_index) + features = cls.get_features_by_feature_class_index(feature_class_index=feature_class_index) + feature_index = len(features) - 1 + feature_position = list(reversed(saasmi_position)) + cls.add_point_to_feature(feature_class_index, feature_index, feature_position, image_number=image_number) + scaled_saasmi_position = cls.scale_position_from_frontend_to_backend(saasmi_position)[:2] + current_scan.saasmi_add_node(image_number=image_number, saasmi_position=scaled_saasmi_position) + + @classmethod + def get_graph_by_graph_type(cls, graph_type, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + atom_network = current_scan.saasmi_get_atom_network(image_number) + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + if graph_type == "oxygen": + graph = atom_network.oxygen_graph + elif graph_type == "silicon": + graph = atom_network.silicon_graph + elif graph_type == "oxygen_silicon": + graph = atom_network.silicon_oxygen_graph + else: + graph = saasmi.rag + return graph + + @classmethod + def _get_node_ids_inside_mask(cls, graph, image_number, apply_image_mask): + if apply_image_mask: + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + image_mask = cls.get_analyse_area_mask_image(image_number) + analyser = saasmi.get_area_analyser() + node_ids = analyser.get_node_ids_inside_image_mask(graph, polygon_image_mask=image_mask) + else: + node_ids = None + return node_ids + + @classmethod + def get_saasmi_graph_node_position_dict(cls, image_number, graph_type, apply_image_mask=False): + graph = cls.get_graph_by_graph_type(graph_type=graph_type, image_number=image_number) + node_ids = cls._get_node_ids_inside_mask(graph, image_number, apply_image_mask) + node_position_dict = {} + for node in graph.nodes: + if node_ids is None or node in node_ids: + try: + node_position_dict[node] = graph.nodes[node]["marker"] + except KeyError: + graph.nodes[node]["marker"] = graph.nodes[node]["centroid"] + node_position_dict[node] = graph.nodes[node]["marker"] + node_position_dict[node] = cls.scale_position_from_backend_to_frontend(node_position_dict[node]) + return node_position_dict + + @classmethod + def get_saasmi_distance_list(cls, image_number, graph_type, apply_image_mask=False): + graph = cls.get_graph_by_graph_type(graph_type=graph_type, image_number=image_number) + node_ids = cls._get_node_ids_inside_mask(graph, image_number, apply_image_mask) + distance_list = [] + for edge in graph.edges: + if node_ids is None: + distance_list.append(graph.edges[edge]['distance']) + else: + for node_id in edge: + if node_id in node_ids: + distance_list.append(graph.edges[edge]['distance']) + break + return distance_list + + @classmethod + def get_saasmi_graph_edges(cls, image_number, graph_type, apply_image_mask=False): + graph = cls.get_graph_by_graph_type(graph_type=graph_type, image_number=image_number) + node_ids = cls._get_node_ids_inside_mask(graph, image_number, apply_image_mask) + edge_list = [] + for edge in graph.edges: + if node_ids is None: + edge_list.append(edge) + else: + for node_id in edge: + if node_id in node_ids: + edge_list.append(edge) + break + return edge_list + + @classmethod + def get_saasmi_node_degree_dict(cls, image_number, graph_type): + graph = cls.get_graph_by_graph_type(graph_type=graph_type, image_number=image_number) + return graph.degree + + @classmethod + @progress.track_process + def recreate_saasmi_network(cls, image_number, atom_network_type): + current_scan = scan_organizier.ScanOrganizer.current_scan + current_scan.saasmi_recreate_atom_network(image_number, atom_network_type) + + @classmethod + def saasmi_get_nearest_element(cls, image_number, position, graph_type): + position = cls.scale_position_from_frontend_to_backend(position) + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + return saasmi.get_nearest_element(position, graph_type=graph_type) + + @classmethod + def saasmi_get_edge_gradient(cls, image_number, edge, graph_type, number_of_points): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + graph = cls.get_graph_by_graph_type(graph_type, image_number) + image = cls.get_2d_image_by_number(image_number) + gradient = saasmi.get_edge_gradient(edge, graph, image, number_of_points) + return gradient + + @classmethod + def export_atom_network_as_csv(cls, image_number, file_path="network.csv"): + header_dict = {} + current_scan = scan_organizier.ScanOrganizer.current_scan + atom_network = current_scan.saasmi_get_atom_network(image_number) + rings = atom_network.atom_rings + image = cls.get_2d_image_by_number(number=image_number) + header_dict["Image Dimension"] = image.shape + header_dict["Number of Ring Centers"] = len(rings) + number_of_silicon_atoms = 0 + number_of_oxygen_atoms = 0 + silicon_atom_dict = {} + oxygen_atom_dict = {} + rag, _ = cls.get_saasmi_rag_and_label(image_number=image_number) + for ring in rings: + for atom in ring.atoms: + if isinstance(atom, OxygenAtom): + oxygen_atom_dict[number_of_oxygen_atoms] = { + "position": list(reversed([int(round(coord)) for coord in atom.position]))} + neighbor_degrees = [] + for node in atom.network_nodes: + neighbor_degrees.append(rag.degree[node]) + oxygen_atom_dict[number_of_oxygen_atoms]["neighbor_degrees"] = neighbor_degrees + number_of_oxygen_atoms += 1 + + elif isinstance(atom, SiliconAtom): + position = [int(round(coord)) for coord in atom.position] + position[0] = image.shape[0] - position[0] + silicon_atom_dict[number_of_silicon_atoms] = { + "position": list(reversed(position))} + neighbor_degrees = [] + for node in atom.network_nodes: + neighbor_degrees.append(rag.degree[node]) + silicon_atom_dict[number_of_silicon_atoms]["neighbor_degrees"] = neighbor_degrees + number_of_silicon_atoms += 1 + header_dict["Number of Silicon Atoms"] = number_of_silicon_atoms + header_dict["Number of Oxygen Atoms"] = number_of_oxygen_atoms + with open(file_path, 'w') as csv_file: + writer = csv.writer(csv_file) + writer.writerow(["Header"]) + for key, value in header_dict.items(): + writer.writerow([key, value]) + writer.writerow(["Silicon Atoms"]) + writer.writerow(["Number", "X", "Y", "Neighbor 1 Degree", "Neighbor 2 Degree", "Neighbor 3 Degree"]) + for key, value in silicon_atom_dict.items(): + line = [] + line.extend(value['position']) + line.extend(value["neighbor_degrees"]) + writer.writerow([key, *line]) + # writer.writerow(["Oxygen Atoms"]) + # writer.writerow(["Number", "X", "Y"]) + # for key, value in oxygen_atom_dict.items(): + # writer.writerow([key, *list(value.values())]) + + @classmethod + def export_saasmi_rag(cls, image_number): + rag, _ = cls.get_saasmi_rag_and_label(image_number=image_number) + pickle.dump(rag, open("rag.pkl", "wb")) + + @classmethod + def export_chosen_network_graph(cls, image_number, graph_type): + graph = cls.get_graph_by_graph_type(graph_type, image_number) + if graph is None: + return False + else: + pickle.dump(graph, open("{}_network.pkl".format(graph_type), "wb")) + return True + + @classmethod + def get_rag_information_dict(cls, image_number, apply_image_mask=False): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + image_shape = cls.get_2d_image_by_number(image_number).shape + rag, _ = cls.get_saasmi_rag_and_label(image_number=image_number) + if apply_image_mask is True: + analyser = saasmi.get_area_analyser() + image_mask = analyser.get_image_mask(image_shape) + selected_node_ids = analyser.get_node_ids_inside_image_mask(rag, image_mask) + else: + selected_node_ids = None + image = cls.get_2d_image_by_number(image_number) + rag_dict = {"positions": [], + "angles": [], + "distances": [], + "degrees": [], + "neighbor_degrees": [], + "triplet_degrees": [], } + for node in rag.nodes: + if selected_node_ids is not None and node not in selected_node_ids: + continue + try: + center = rag.nodes[node]["marker"] + except (KeyError, IndexError): + center = rag.nodes[node]["centroid"] + position = point_utilities.mirror_points_image_diagonal(image.shape, center) + rag_dict["positions"].append(position) + rag_dict["degrees"].append(rag.degree[node]) + neighbor_list = [node] + for edge in rag.edges: + if node in edge: + neighbor_node = [edge_node for edge_node in edge if node != edge_node][0] + neighbor_list.append(neighbor_node) + rag_dict["neighbor_degrees"].append( + np.array([rag.degree[neighbor_node] for neighbor_node in neighbor_list])) + for edge in rag.edges: + if selected_node_ids is not None and not [node for node in edge if node in selected_node_ids]: + continue + distance = rag.edges[edge]["distance"] + angle = rag.edges[edge]["angle"] + rag_dict["angles"].append(angle) + rag_dict["distances"].append(distance) + rag_tripelts = saasmi.get_degree_triples() + rag_dict["triplet_degrees"] = rag_tripelts + return rag_dict + + @classmethod + def export_saasmi_properties(cls, image_number, apply_image_mask=False): + image = cls.get_2d_image_by_number(image_number) + image_shape = image.shape + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + atom_network = current_scan.saasmi_get_atom_network(image_number) + if apply_image_mask is True: + analyser = saasmi.get_area_analyser() + image_mask = analyser.get_image_mask(image_shape) + oxygen_selected_nodes = analyser.get_node_ids_inside_image_mask(graph=atom_network.oxygen_graph, + polygon_image_mask=image_mask) + silicon_selected_nodes = analyser.get_node_ids_inside_image_mask(graph=atom_network.silicon_graph, + polygon_image_mask=image_mask) + silicon_oxygen_selected_nodes = [*oxygen_selected_nodes, *silicon_selected_nodes] + else: + oxygen_selected_nodes = None + silicon_selected_nodes = None + silicon_oxygen_selected_nodes = None + silicon_distances = atom_network.get_network_distances(atom_type="silicon", + selected_node_ids=silicon_selected_nodes) + oxygen_distances = atom_network.get_network_distances(atom_type="oxygen", + selected_node_ids=oxygen_selected_nodes) + silicon_oxygen_distances = atom_network.get_network_distances(atom_type=None, + selected_node_ids=silicon_oxygen_selected_nodes) + silicon_angles = atom_network.get_network_angles(atom_type="silicon", selected_node_ids=silicon_selected_nodes) + oxygen_angles = atom_network.get_network_angles(atom_type="oxygen", selected_node_ids=oxygen_selected_nodes) + silicon_positions_dict = cls.get_saasmi_graph_node_position_dict(image_number, graph_type="silicon", + apply_image_mask=apply_image_mask) + silicon_positions = list(silicon_positions_dict.values()) + silicon_positions = point_utilities.mirror_point_list_image_diagonale(image_shape, silicon_positions) + oxygen_positions_dict = cls.get_saasmi_graph_node_position_dict(image_number, graph_type="oxygen", + apply_image_mask=apply_image_mask) + oxygen_positions = list(oxygen_positions_dict.values()) + oxygen_positions = point_utilities.mirror_point_list_image_diagonale( + image_shape, oxygen_positions) + + oxygen_silicon_oxygen_angles = atom_network.get_atom_triangle_angle( + selected_node_ids=silicon_oxygen_selected_nodes) + silicon_oxygen_angles = atom_network.get_network_angles(atom_type=None, + selected_node_ids=silicon_oxygen_selected_nodes) + rag_dict = cls.get_rag_information_dict(image_number, apply_image_mask) + try: + with h5py.File("saasmi_information.h5", 'w') as hf_file: + hf_file.create_dataset("oxygen/" + "distances", + data=oxygen_distances) + hf_file.create_dataset("oxygen/" + "angles", + data=oxygen_angles) + hf_file.create_dataset("oxygen/" + "positions", + data=oxygen_positions) + hf_file.create_dataset("silicon/" + "distances", + data=silicon_distances) + hf_file.create_dataset("silicon/" + "angles", + data=silicon_angles) + hf_file.create_dataset("silicon/" + "positions", + data=silicon_positions) + hf_file.create_dataset("silicon_oxygen/" + "distances", + data=silicon_oxygen_distances) + hf_file.create_dataset("silicon_oxygen/" + "angles", + data=silicon_oxygen_angles) + hf_file.create_dataset("silicon_oxygen/" + "silicon_oxygen_silicon_angle", + data=oxygen_silicon_oxygen_angles) + for data_set, values in rag_dict.items(): + if data_set == "neighbor_degrees": + for ring_index, neighbor_degrees in enumerate(values): + hf_file.create_dataset("ring/{}/{}".format(data_set, ring_index), + data=neighbor_degrees) + elif data_set == "triplet_degrees": + for triplet_index, triplet_node_degree_list in enumerate(values): + hf_file.create_dataset("ring/{}/{}".format(data_set, triplet_index), + data=triplet_node_degree_list) + else: + hf_file.create_dataset("ring/" + data_set, + data=values) + except OSError as e: + log.exception() + log.info("Could not write the file, maybe used by another program") + + @classmethod + def get_scan_information(cls): + try: + current_scan = scan_organizier.ScanOrganizer.current_scan + scan_information = current_scan.scan_information + scan_information_str = "" + for key, value in scan_information.items(): + scan_information_str += "{}: {} \n".format(key, value) + scan_image_information_str = "" + scan_image_information = current_scan.get_scan_image_information() + for key, value in scan_image_information.items(): + scan_image_information_str += "{}: {} \n".format(key, value) + information_string = "Scan Information \n" + scan_information_str + "\n" + "Scan Image Information \n" + scan_image_information_str + return information_string + except (AttributeError, KeyError, IndexError): + return "" + + @classmethod + def add_saasmi_area_analyser_polygon(cls, image_number, area_type, polygon): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + for point_id, point in enumerate(polygon): + polygon[point_id] = cls.scale_position_from_frontend_to_backend(point) + analyser.add_polygon(polygon, area_type=area_type) + image_shape = current_scan.images[image_number].shape + analyser.generate_sub_graph(image_shape=image_shape, rag=saasmi.rag) + + @classmethod + def add_saasmi_area_analyser_input_mask(cls, image_number, image_mask_file_path): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + input_image = imageio.imread(image_mask_file_path) + input_mask = input_image > 0 + analyser.add_input_image_mask(input_mask, image_mask_file_path) + image_shape = current_scan.images[image_number].shape + analyser.generate_sub_graph(image_shape=image_shape, rag=saasmi.rag) + + @classmethod + def get_analyse_areas_string_list(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + area_list = analyser.area_list + area_string_list = [] + for polygon_list, area_type in area_list: + area_string_list.append("Area Type: {}, Number of Points {}".format(area_type, len(polygon_list))) + return area_string_list + + @classmethod + def get_analyse_area_mask_image(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + image = cls.get_2d_image_by_number(image_number) + image_shape = image.shape + image_mask = analyser.get_image_mask(image_shape) + return image_mask + + @classmethod + def get_analyse_input_image_mask_file_path_list(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + image_mask_list = analyser.input_mask_and_file_path_list + image_mask_file_path_list = [file_path for image_mask, file_path in image_mask_list] + return image_mask_file_path_list + + @classmethod + def remove_analyse_input_mask_file_path_list(cls, image_number, mask_index): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + analyser.input_mask_and_file_path_list.pop(mask_index) + + @classmethod + def remove_analyse_polygon(cls, image_number, polygon_index): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) # type: SAASMI + analyser = saasmi.get_area_analyser() + analyser.area_list.pop(polygon_index) + + @classmethod + def auto_save_function(cls): + cls.save_scan_in_file("autosave.vsy") + + @classmethod + def get_saasmi_atom_network_polygons(cls, image_number, apply_image_mask): + image_mask = cls.get_analyse_area_mask_image(image_number) + current_scan = scan_organizier.ScanOrganizer.current_scan + atom_network = current_scan.saasmi_get_atom_network(image_number) + network_polygoons = atom_network.network_polygons + polygons_inside_of_mask = [] + for polygon in network_polygoons: + polygon_inside = True + for point in polygon: + if apply_image_mask and image_mask[int(point[0]), int(point[1])] == 0: + polygon_inside = False + if polygon_inside is True and len(polygon) > 2: + polygons_inside_of_mask.append(copy.copy(polygon)) + # SCALE POLYGONS FOR FRONTEND + for polygon in polygons_inside_of_mask: + for point_id, point in enumerate(polygon): + point = cls.scale_position_from_backend_to_frontend(point) + polygon[point_id] = point + return polygons_inside_of_mask + + @classmethod + def get_ideal_wave_form(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + ideal_wave_forms = current_scan.ideal_wave_forms + if ideal_wave_forms is None: + return None + if len(ideal_wave_forms) > 1: + ideal_wave_form = ideal_wave_forms[image_number % 2] + else: + ideal_wave_form = ideal_wave_forms[0] + ideal_wave_form = copy.copy(ideal_wave_form) + return ideal_wave_form + + @classmethod + def get_measuring_point_informations(cls, image_number, measuring_points, middle_point=False, point_index=0): + current_scan = scan_organizier.ScanOrganizer.current_scan + information_dict = current_scan.get_measuring_point_information(image_number, measuring_points, middle_point, + point_index) + return information_dict + + @classmethod + def get_feature_class_visualization_by_item_name(cls, item_name, feature_class_name=None, feature_class_index=None): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_class_name is None: + feature_class_name = cls.get_feature_classes_list()[feature_class_index] + feature_class_index, feature_class = feature_organizer.get_feature_class_by_string(feature_class_name) + try: + feature_class_visualization = feature_class.visualization_representation + except AttributeError: + feature_class_visualization = None + if feature_class_visualization is None: + return cls.get_config_by_key_sub_key("feature_settings", item_name) + else: + return feature_class.visualization_representation[item_name] + + @classmethod + def get_threshold_for_equal_jumps_in_selected_feature_classes(cls, image_range, selected_feature_classes, + selected_feature_class_dict): + jump_distances_list = [] + for feature_class_name in selected_feature_classes: + feature_list = selected_feature_class_dict[feature_class_name] + jump_distances_list.append(cls.get_feature_class_movement_distance(image_range, + feature_class_name, + feature_list)) + feature_jump_thresholds = {} + if len(jump_distances_list) == 0: + return {} + + # SORT JUMP LIST BY DISTANCE + for index, jump_distance_list in enumerate(jump_distances_list): + jump_distance_list.sort(reverse=True) + + index_list = [0] * len(jump_distances_list) + value_list = [0] * len(jump_distances_list) + jumps_above_threshold_list = [0] * len(jump_distances_list) + while True: + try: + for i, index in enumerate(index_list): + value_list[i] = jump_distances_list[i][index] + max_index = value_list.index(max(value_list)) + max_value = jump_distances_list[max_index][index_list[max_index]] + jumps_above_threshold_list[max_index] += 1 + index_list[max_index] += 1 + if len(set(jumps_above_threshold_list)) == 1: + feature_jump_thresholds[jumps_above_threshold_list[0]] = max_value + + except IndexError: + break + return feature_jump_thresholds + + @classmethod + def get_features_exceed_maximum_number_of_points(cls, maximum_number_of_points): + feature_organizer = scan_organizier.ScanOrganizer.current_scan.feature_organizer_instance + features_exceed_maximum_number_of_points = feature_organizer.get_features_exceed_maximum_number_of_points( + maximum_number_of_points) + return features_exceed_maximum_number_of_points + + @classmethod + def get_2d_average_image(cls): + number_of_images = cls.get_maximum_image_index() + average_image = None + image_addition_counter = None + for image_number in range(number_of_images + 1): + image = cls.get_2d_image_by_number(image_number) + if average_image is None: + image_addition_counter = ~np.isnan(image) + image_addition_counter = image_addition_counter.astype(int) + average_image = np.nan_to_num(image) + + else: + image_addition_counter += ~np.isnan(image) + average_image += np.nan_to_num(image) + np.seterr(divide='ignore', invalid='ignore') + average_image /= image_addition_counter + np.seterr(divide='warn', invalid='warn') + return average_image + + @classmethod + def get_max_image(cls): + number_of_images = cls.get_maximum_image_index() + max_image = None + for image_number in range(number_of_images + 1): + image = cls.get_2d_image_by_number(image_number) + if max_image is None: + max_image = copy.copy(image) + else: + image[np.isnan(image)] = float('-inf') + max_image[np.isnan(max_image)] = float('-inf') + max_image = np.maximum(max_image, image) + max_image[np.isinf(max_image)] = float('nan') + return max_image + + @classmethod + def get_min_image(cls): + number_of_images = cls.get_maximum_image_index() + min_image = None + for image_number in range(number_of_images + 1): + image = cls.get_2d_image_by_number(image_number) + if min_image is None: + min_image = copy.copy(image) + else: + image[np.isnan(image)] = float('inf') + min_image[np.isnan(min_image)] = float('inf') + min_image = np.minimum(min_image, image) + min_image[np.isinf(min_image)] = float('nan') + return min_image + + @classmethod + @progress.track_process + def generate_feature_rags(cls, force_recreate=False): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated and not force_recreate: + return + for image_number in range(cls.get_maximum_image_index() + 1): + if image_number in feature_organizer.rag_dict: + rag_exists = True + else: + rag_exists = False + if force_recreate or not rag_exists: + image = cls.get_2d_image_by_number(image_number) + rag = feature_organizer.get_feature_rag(image, image_number=image_number) + if rag is not None: + feature_organizer.rag_dict[image_number] = rag + feature_organizer.rag_generated = True + + @classmethod + def get_feature_rag_non_oxygen_neigbhbor_information(cls, selected_feature_classes, selected_feature_dict): + feature_class_non_oxygen_value_dict = cls.get_non_oxygen_feature_class_neighbor_values(selected_feature_classes, + selected_feature_dict) + neighbor_duration_dict = {} + neighbor_non_oxygen_changes_direction = {} + for feature_class_name, feature_class_info in feature_class_non_oxygen_value_dict.items(): + if feature_class_name not in neighbor_duration_dict: + neighbor_duration_dict[feature_class_name] = {} + neighbor_non_oxygen_changes_direction[feature_class_name] = {} + for feature_index, non_oxygen_neighbor_values in feature_class_info.items(): + if feature_index not in neighbor_duration_dict[feature_class_name]: + neighbor_duration_dict[feature_class_name][feature_index] = {} + neighbor_non_oxygen_changes_direction[feature_class_name][feature_index] = [] + prev_value = None + for non_oxygen_value in non_oxygen_neighbor_values: + if math.isnan(non_oxygen_value): + prev_value = None + continue + if prev_value is not None: + neighbor_non_oxygen_changes_direction[feature_class_name][feature_index].append( + (prev_value, non_oxygen_value, non_oxygen_value - prev_value)) + if non_oxygen_value not in neighbor_duration_dict[feature_class_name][feature_index]: + neighbor_duration_dict[feature_class_name][feature_index][non_oxygen_value] = 1 + else: + neighbor_duration_dict[feature_class_name][feature_index][non_oxygen_value] += 1 + prev_value = non_oxygen_value + return neighbor_duration_dict, neighbor_non_oxygen_changes_direction + + @classmethod + def get_first_non_oxygen_feature_class(cls): + all_feature_classes = cls.get_feature_classes_list() + for feature_class_name in all_feature_classes: + if feature_class_name.lower() != "oxygen": + return feature_class_name + + @classmethod + def get_non_oxygen_feature_class_neighbor_values(cls, selected_feature_classes, selected_feature_dict): + rag_graph_info = cls.get_feature_rag_jump_graph_info(selected_feature_classes, + selected_feature_dict) + non_oxygen_feature_class = cls.get_first_non_oxygen_feature_class() + non_oxygen_neighbor_value_dict = {} + for feature_class_name in selected_feature_classes: + non_oxygen_neighbor_value_dict[feature_class_name] = {} + for feature_index in selected_feature_dict[feature_class_name]: + non_oxygen_neighbor_value_dict[feature_class_name][feature_index] = [] + for image_number in range(len(rag_graph_info)): + for feature_class_name in selected_feature_classes: + for feature_index in non_oxygen_neighbor_value_dict[feature_class_name]: + try: + neighbor_dict = rag_graph_info[image_number][feature_class_name][feature_index] + if non_oxygen_feature_class in neighbor_dict: + non_oxygen_value = neighbor_dict[non_oxygen_feature_class] + else: + non_oxygen_value = 0 + except KeyError: + non_oxygen_value = float("nan") + non_oxygen_neighbor_value_dict[feature_class_name][feature_index].append(non_oxygen_value) + return non_oxygen_neighbor_value_dict + + @classmethod + def _generate_feature_rag_non_oxygen_csv_string(cls, selected_feature_classes, selected_feature_dict): + non_oxygen_neighbor_duration, non_oxygen_neighbor_direction = cls.get_feature_rag_non_oxygen_neigbhbor_information( + selected_feature_classes, selected_feature_dict) + time_stamps = cls.get_time_stamps() + time_stamps_delta = time_stamps[1] - time_stamps[0] + non_oxygen_csv_string = "" + for feature_class_name, feature_class_information in non_oxygen_neighbor_duration.items(): + if len(feature_class_information) == 0: + continue + non_oxygen_csv_string += "{}\n".format(feature_class_name) + for feature_index, feature_index_information in feature_class_information.items(): + non_oxygen_csv_string += "feature index,total time, adjacent vacancies, frames, time, ratio, jump to less adjacent vacancies, jump to more adjacent vacancies \n".format( + feature_index) + change_direction_dict = Counter(non_oxygen_neighbor_direction[feature_class_name][feature_index]) + duration_total = sum(feature_index_information.values()) + non_oxygen_csv_string += "{}, {}\n".format(feature_index, duration_total * time_stamps_delta) + for non_oxygen_number, duration in feature_index_information.items(): + non_oxygen_csv_string += ",,{value},{duration_image}, {duration_time},{ratio} \n".format( + value=non_oxygen_number, + duration_image=duration, + duration_time=duration * time_stamps_delta, + ratio=duration / duration_total) + jump_up, jump_down = 0, 0 + for direction_list, occurence in change_direction_dict.items(): + if direction_list[0] == non_oxygen_number and direction_list[2] > 0: + jump_up += occurence + elif direction_list[0] == non_oxygen_number and direction_list[2] < 0: + jump_down += occurence + non_oxygen_csv_string += ",,,, ,, {}, {} \n".format(jump_down, jump_up) + non_oxygen_csv_string += "\n" + non_oxygen_csv_string += "\n\n" + return non_oxygen_csv_string + + @classmethod + def export_feature_rag_information(cls, file_path, selected_feature_classes, selected_feature_dict): + try: + file_path, extension = file_path.split(".") + except ValueError: + extension = "csv" + non_oxygen_csv_string = cls._generate_feature_rag_non_oxygen_csv_string(selected_feature_classes, + selected_feature_dict) + print(non_oxygen_csv_string) + + if extension == "csv": + with open(file_path + "." + extension, 'w', newline='') as csvfile: + csvfile.write(non_oxygen_csv_string) + else: + log.info("{} file format is not supported".format(extension)) + + @classmethod + def get_feature_rag_node_dict(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated is False: + cls.generate_feature_rags() + try: + rag = feature_organizer.rag_dict[image_number] + except KeyError: + return {} + node_dict = {} + for node in rag.nodes: + node_feature_class = rag.nodes[node]["feature_class_name"] + node_position = rag.nodes[node]['marker'] + node_position = cls.scale_position_from_backend_to_frontend(node_position) + if node_feature_class in node_dict: + node_dict[node_feature_class].append(node_position) + else: + node_dict[node_feature_class] = [node_position] + return node_dict + + @classmethod + def get_feature_rag_jump_information(cls, selected_feature_classes, selected_feature_dict): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated is False: + cls.generate_feature_rags() + rag_information = feature_organizer.get_rag_jump_information(selected_feature_classes, selected_feature_dict) + return rag_information + + @classmethod + def get_feature_rag_number_of_image(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + return len(feature_organizer.rag_dict) + + @classmethod + def get_feature_rag_edge_list(cls, image_number): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated is False: + cls.generate_feature_rags() + try: + rag = feature_organizer.rag_dict[image_number] + except KeyError: + return [] + + edge_list = [] + for edge in rag.edges: + start = rag.nodes[edge[0]]['marker'] + end = rag.nodes[edge[1]]['marker'] + start = cls.scale_position_from_backend_to_frontend(start) + end = cls.scale_position_from_backend_to_frontend(end) + edge_list.append([start, end]) + return edge_list + + @classmethod + def get_feature_rag_jump_graph_info(cls, selected_feature_classes, selected_feature_dict): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated is False: + cls.generate_feature_rags() + rag_jump_graph_info = {} + for image_number, rag in feature_organizer.rag_dict.items(): + rag_jump_graph_info[image_number] = feature_organizer.get_rag_neigbhbor_dict(rag, selected_feature_classes, + selected_feature_dict) + return rag_jump_graph_info + + @classmethod + def get_feature_rag_unique_jump_constellation(cls, selected_feature_classes, selected_feature_dict): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_organizer = current_scan.feature_organizer_instance + if feature_organizer.rag_generated is False: + cls.generate_feature_rags() + rag_information_dict = feature_organizer.get_rag_jump_information(selected_feature_classes, + selected_feature_dict) + changes_dict = {} + potential_wrong_neighborhood_list = [] + for image_number, neighbor_change_list in rag_information_dict.items(): + for neighbor_changes in neighbor_change_list: + node, change_list = neighbor_changes + skip = False + for change in change_list: + number_of_neighbors = 0 + for feature_class_name, occurency in change.items(): + number_of_neighbors += occurency + if number_of_neighbors < 6: + skip = True + elif number_of_neighbors > 6: + potential_wrong_neighborhood_list.append([image_number, change]) + skip = True + change_1, change_2 = change_list + if skip is False: + change = (str(change_1), str(change_2)) + if change in changes_dict: + changes_dict[change] += 1 + else: + changes_dict[change] = 1 + if len(potential_wrong_neighborhood_list) > 0: + log_info_str = "" + for image_number, neighborhood in potential_wrong_neighborhood_list: + log_info_str += "Image Number {}: Neighborhood {}\n".format(image_number, pprint.pformat(neighborhood)) + log.info("Potential Neighborhood Errors in:\n {}".format(log_info_str)) + + return changes_dict + + @classmethod + def get_saasmi_edge_color_bar(cls, graph_type, selector): + current_scan = scan_organizier.ScanOrganizer.current_scan + color_bar_dict = current_scan.color_bar_dict + try: + color_bar_info = color_bar_dict[(graph_type, selector)] + except KeyError: + color_bar_info = cls.get_config_by_key_sub_key("saasmi", + "{}_{}_color_map_dict".format(graph_type, selector)) + return color_bar_info + + @classmethod + def set_saasmi_edge_color_bar(cls, graph_type, selector, color_map_dict): + current_scan = scan_organizier.ScanOrganizer.current_scan + color_bar_dict = current_scan.color_bar_dict + color_bar_dict[(graph_type, selector)] = color_map_dict + + @classmethod + def check_saasmi_edges(cls, image_number, graph_type, number_of_points): + current_scan = scan_organizier.ScanOrganizer.current_scan + saasmi = current_scan.get_saasmi(image_number) + graph = cls.get_graph_by_graph_type(graph_type, image_number) + image = cls.get_2d_image_by_number(image_number) + saasmi.check_saasmi_edges(graph, image, number_of_points) + + @classmethod + def feature_grid_list_add_point(cls, point): + current_scan = scan_organizier.ScanOrganizer.current_scan + point_backend = cls.scale_position_from_frontend_to_backend(point) + current_scan.add_point_to_feature_grid_list(point_backend) + + @classmethod + def feature_grid_list_remove_point(cls, point): + current_scan = scan_organizier.ScanOrganizer.current_scan + point_backend = cls.scale_position_from_frontend_to_backend(point) + current_scan.remove_point_to_feature_grid_list(point_backend) + + @classmethod + def get_feature_grid_list(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_grid_point_list = current_scan.get_feature_grid_point_list() + feature_grid_point_list_front_end = [cls.scale_position_from_backend_to_frontend(point) for point in + feature_grid_point_list] + return feature_grid_point_list_front_end + + @classmethod + def get_feature_grid_nearest_position(cls, position): + current_scan = scan_organizier.ScanOrganizer.current_scan + backend_position = cls.scale_position_from_frontend_to_backend(position) + feature_grid_point_list = current_scan.get_feature_grid_point_list() + point_list_array = np.asarray(feature_grid_point_list) + dist_2 = np.sum((point_list_array - backend_position[:2]) ** 2, axis=1) + index = int(np.argmin(dist_2)) + new_point = [*feature_grid_point_list[index], *backend_position[2:]] + new_point_front_end = cls.scale_position_from_backend_to_frontend(new_point) + return new_point_front_end + + @classmethod + @progress.track_process + def apply_feature_grid_to_features(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_grid_point_list = current_scan.get_feature_grid_point_list() + feature_organizer = current_scan.feature_organizer_instance + feature_organizer.apply_feature_grid_to_feature_points(feature_grid_point_list) + + @classmethod + def set_feature_grid_visualization_settings(cls, feature_grid_visualization_settings): + cls.set_config_by_key_sub_key("feature_grid_settings", "visualization_settings", + feature_grid_visualization_settings) + current_scan = scan_organizier.ScanOrganizer.current_scan + current_scan.set_feature_grid_visualization_settings(feature_grid_visualization_settings) + + @classmethod + def get_feature_grid_visualization_settings(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + visualization_settings = current_scan.get_feature_grid_visualization_settings() + if visualization_settings is None: + return cls.get_config_by_key_sub_key("feature_grid_settings", "visualization_settings") + else: + return visualization_settings + + @classmethod + def clear_feature_grid_list(cls): + current_scan = scan_organizier.ScanOrganizer.current_scan + feature_grid_point_list = current_scan.get_feature_grid_point_list() + feature_grid_point_list.clear() + + @classmethod + def get_saved_filter_names(cls, filter_category, filter_class): + filter_class_names = filter_organizer.FilterOrganizer.get_saved_filter_names(filter_category, filter_class) + return filter_class_names + + @classmethod + def add_filter_to_saved_filters(cls, filter_category, filter_class, filter_name, filter_inputs): + filter_organizer.FilterOrganizer.add_filter_to_saved_filters(filter_category, filter_class, filter_name, + filter_inputs) + + @classmethod + def get_saved_filter_dict(cls, filter_category, filter_class, filter_name): + return filter_organizer.FilterOrganizer.get_saved_filter_inputs(filter_category, filter_class, filter_name) + + @classmethod + def get_plot_dict(cls, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + return current_scan.plotable_dict + + @classmethod + def check_plot_dict(cls, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + if len(current_scan.plotable_dict) == 0: + file_path_h5 = current_scan.file_path + return cls.generate_plotable_dict_from_h5_file(file_path_h5) + else: + return True + + @classmethod + def generate_plotable_dict_from_h5_file(cls, file_path, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + if os.path.exists(file_path) and file_path.split(".")[-1] == "h5": + with h5py.File(file_path, 'r') as file: + try: + h5_data = file['data'] + except KeyError: + h5_data = file + plot_dict = {} + h5_reader.generate_plot_dict_by_data(h5_data, plot_dict) + current_scan.plotable_dict = plot_dict + return True + return False + + @classmethod + def get_density_array_by_image_number(cls, number, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + if current_scan.density_arrays: + density_array = copy.copy(current_scan.density_arrays[number]) + return density_array + + @classmethod + def export_lines_as_csv(cls, scan_index=None): + current_scan = cls.get_scan_by_scan_index(scan_index) + feature_organizer = current_scan.feature_organizer_instance + line_pandas = feature_organizer.get_feature_lines() + line_pandas.sort_values(by=['image_number']) + csv_file_name = 'lines-{}.csv'.format(datetime.now().strftime("%Y-%m-%d-%H-%M-%S")) + line_pandas.to_csv(csv_file_name, index=False) + log.info("Exported the line infos to file `{}`".format(csv_file_name)) diff --git a/model_view/neural_networks.py b/model_view/neural_networks.py new file mode 100644 index 0000000..ce1f1eb --- /dev/null +++ b/model_view/neural_networks.py @@ -0,0 +1,144 @@ + + +import re +import subprocess + +import numpy as np + +from model.data.exporter import export_yolo_data + +import os +import logging + +log = logging.getLogger(__name__) + +YOLO_LABELS_REGEX = r"(.*) (.*) (.*) (.*) (.*)" + + +def check_yolov5_path(path): + """Checks if the path has a train.py file""" + # TODO: check if git repository is correct and if there is a correct venv or something + return os.path.exists(os.path.join(path, "train.py")) + + +def check_yolo_training_data_directory(path): + necessary_elements = ["images", "labels", "visualyse.yaml"] + if not os.path.exists(path): + return False + elements_in_directory = os.listdir(path) + for necessary_element in necessary_elements: + if necessary_element not in elements_in_directory: + return False + return True + + +def generate_training_data(model_view, training_data_path): + if not os.path.exists(training_data_path): + if not os.path.isdir(training_data_path): + raise NotADirectoryError("Path {} is a file, should be a directory".format(training_data_path)) + os.makedirs(training_data_path) + if not os.listdir(training_data_path): + export_yolo_data.generate_training_data(model_view, training_data_path) + + +def generate_detection_data(model_view, data_path): + if not os.path.exists(data_path): + if not os.path.isdir(data_path): + raise NotADirectoryError("Path {} is a file, should be a directory".format(data_path)) + os.makedirs(data_path) + export_yolo_data.generate_detection_data(model_view, data_path) + + +def get_training_command(model_view, yolo_path, interpreter_path, data_path, number_of_epochs, batch_size, + weight_path="", name="", + additional_parameters=""): + image = model_view.get_2d_image_by_number(0) + image_resolution = max(image.shape) + data_yaml_path = os.path.join(data_path, "visualyse.yaml") + if name == "": + name = "exp" + if weight_path == "": + weight_path = "yolov5s.pt" + command = "cd {yolo_path};{interpreter_path} train.py --img {image_resolution} --batch {batch_size} --epochs {number_of_epochs} --data {data_yaml_path} --weights {weight_path} --name {name} {additional_parameters}".format( + yolo_path=yolo_path, + interpreter_path=interpreter_path, + image_resolution=image_resolution, + batch_size=batch_size, + number_of_epochs=number_of_epochs, + data_yaml_path=data_yaml_path, + weight_path=weight_path, + name=name, + additional_parameters=additional_parameters) + return command + + +def get_detection_command(model_view, yolo_path, interpreter_path, data_path, weight_path, confidence_threshold=0.5, + name="", + additional_parameters=""): + """python detect.py --source ../TrainingData/training_data_new/images/ --im 100 --weights runs/train/exp5/weights/best.pt --save-txt --line-thickness 1 --view-img --hide-labels --conf-thres 0.5 + + + """ + image = model_view.get_2d_image_by_number(0) + image_resolution = max(image.shape) + if name == "": + name = "exp" + if weight_path == "": + weight_path = "yolov5s.pt" + detection_data_path = os.path.join(data_path, "detection_images") + command = "cd {yolo_path};{interpreter_path} detect.py --source {detection_data_path} --im {image_resolution} --weights {weight_path} --name {name} --save-txt --line-thickness 1 --hide-labels --conf-thres {confidence_threshold} {additional_parameters}".format( + yolo_path=yolo_path, + interpreter_path=interpreter_path, + image_resolution=image_resolution, + weight_path=weight_path, + detection_data_path=detection_data_path, + name=name, + additional_parameters=additional_parameters, + confidence_threshold=confidence_threshold) + return command + + +def start_training(model_view, yolo_path, interpreter_path, data_path, number_of_epochs, batch_size, weight_path="", + name="", + additional_parameters="", + call_back_print_function=print): + """Trains a model from the current data in the program. Needs a venv in the yolov5 path with all necessary libraries installed""" + + if not check_yolo_training_data_directory(data_path): + generate_training_data(model_view, data_path) + command = get_training_command(model_view, yolo_path, interpreter_path, data_path, number_of_epochs, batch_size, + weight_path, name, + additional_parameters) + log.info("Command to execute: {}".format(command)) + # TODO LOAD VENV + popen = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, universal_newlines=True) + for stdout_line in popen.stdout: + if stdout_line != "": + call_back_print_function(stdout_line) + popen.stdout.close() + return_code = popen.wait() + + +def start_detecting(model_view, yolo_path, interpreter_path, data_path, weight_path, confidence_threshold, name="", + additional_parameters="", call_back_print_function=print): + command = get_detection_command(model_view, yolo_path, interpreter_path, data_path, weight_path, + confidence_threshold, name, + additional_parameters) + generate_detection_data(model_view, data_path) + log.info("Command to execute: {}".format(command)) + # TODO LOAD VENV + popen = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, universal_newlines=True) + labels_path_relativ = "" + for stdout_line in popen.stdout: + if stdout_line != "": + if "labels saved" in stdout_line: + # Get the path where the results are saved and remove the newline + labels_path_relativ = stdout_line.split(" ")[-1][:-1] + + print(stdout_line) + popen.stdout.close() + return_code = popen.wait() + if labels_path_relativ == "": + log.info("The path to the labels was not found") + return False + labels_path = os.path.join(yolo_path, labels_path_relativ) diff --git a/model_view/process_control/__init__.py b/model_view/process_control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model_view/process_control/process_handler.py b/model_view/process_control/process_handler.py new file mode 100644 index 0000000..c539bac --- /dev/null +++ b/model_view/process_control/process_handler.py @@ -0,0 +1,15 @@ +class ProcessHandler(): + def __init__(self, progress_handler): + self.task_list = list() + self.current_task_number = 0 + + def add_task(self, task, parameters=None, keyword_parameters=None): + self.task_list.append((task,parameters,keyword_parameters)) + + + def run_tasks(self): + for task in self.task_list: + task_function = task[0] + task_parameters = task[1] + task_keyword_parameters = task[2] + task_function(*task_parameters, **task_keyword_parameters) \ No newline at end of file diff --git a/model_view/process_control/progress_handler.py b/model_view/process_control/progress_handler.py new file mode 100644 index 0000000..b1945b3 --- /dev/null +++ b/model_view/process_control/progress_handler.py @@ -0,0 +1,43 @@ +import logging + +from view import progress_representations + +log = logging.getLogger(__name__) + + +class ProgressHandler: + def __init__(self, progress_representation=None): + self.progress_representation = progress_representation # type: progress_representations.ProgressRepresentation + self.percentage_value = 0 + self.message_text = "" + self.cancel_flag = False + + def set_progress_representation(self, new_representation): + self.progress_representation = new_representation + + def process_started(self, process_name): + if self.progress_representation is not None: + return self.progress_representation.process_started(process_name) + else: + log.info("Process started") + return True + + def process_finished(self): + if self.progress_representation is not None: + self.progress_representation.process_finished() + else: + log.info("Process finished") + + def generate_logging_output(self): + log.info( + "{process_message} ({percentage}%)".format(percentage=self.percentage_value, + process_message=self.message_text)) + + def process_events(self): + if self.progress_representation is not None: + self.progress_representation.process_events() + + + + +default_progress_handler = ProgressHandler() diff --git a/overview b/overview new file mode 100644 index 0000000..c30fa77 --- /dev/null +++ b/overview @@ -0,0 +1,52 @@ +Übersicht: + File Formats: + Import: + -h5 + -gwy + - sxm + - (png, jpg) + - avi + - own file-formats (scan-zip, vsy) + Export: + - Image (png, jpg) + - Video (ogv, avi, mp4) + - Gwyddion File + - Zip File + - Vsy Object + + Filters: + 1D: + 1D DC Remover + 1D Hann Smoothing + 1D Gaussian + 1D Brickwall Bandpass + 1D Brickwall Bandstop + 1D Butterworth Bandpass + 1D Butterworth Bandstop + X Correlation + Y Correlation + Frequency Remover + Frequency Manipulator + 2D: + DC Removal + Linear Plane Removal + Invert Image + Fourier Amplitude Filter + 2D Gaussian + Canny Filter + Percentile Filter + 2D Principal Component Analysis + Local Maxima Filter(Fourier) + 2D Brickwall Bandpass + 2D Butterworth Bandpass + 2D Brickwall Bandstop + 2D Butterworth Bandstop + + Feature Detection: + Laplacian of Gaussian - Blob Feature Detection + Speeded Up Robust Features - SURF + Simple Blob Feature Detector + + Feature Organizer: + Full customizable feature structure (Feature-Class, Feature-Number, Point-List) + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e1fb94e --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +filterwarnings = ignore:.*Polyfit.* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..74f3d22 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +opencv-contrib-python +PyQt5~=5.14.1 +vtk +numpy~=1.18.1 +matplotlib~=3.1.3 +h5py~=2.10.0 +gwyfile~=0.2.0 +nanonispy~=1.0.8 +scikit-image~=0.16.2 +imageio~=2.8.0 +Pillow~=7.0.0 +pandas~=1.0.3 +scipy~=1.4.1 +PyYAML~=5.3.1 +PsychoPy~=2020.1.2 +scikit-learn~=0.22.2.post1 +seaborn~=0.10.1 +networkx~=2.4 +pyamg~=4.0.0 +cvlib==0.2.5 +tensorflow +pyqode.python +pyqode.json \ No newline at end of file diff --git a/requirements_from_pip_freeze.txt b/requirements_from_pip_freeze.txt new file mode 100644 index 0000000..06dcece --- /dev/null +++ b/requirements_from_pip_freeze.txt @@ -0,0 +1,117 @@ +altgraph==0.17 +arabic-reshaper==2.0.15 +astroid==2.4.2 +astunparse==1.6.3 +attrs==19.3.0 +backcall==0.2.0 +certifi==2020.4.5.2 +cffi==1.14.0 +chardet==3.0.4 +cryptography==2.9.2 +cycler==0.10.0 +decorator==4.4.2 +distro==1.5.0 +dukpy==0.2.3 +esprima==4.0.1 +et-xmlfile==1.0.1 +freetype-py==2.1.0.post1 +future==0.18.2 +gevent==20.6.1 +gitdb==4.0.5 +GitPython==3.1.3 +glfw==1.11.2 +greenlet==0.4.16 +gwyfile==0.2.0 +h5py==2.10.0 +idna==2.9 +imageio==2.8.0 +imageio-ffmpeg==0.4.2 +importlib-metadata==1.7.0 +ipykernel==5.3.4 +ipython==7.16.1 +ipython-genutils==0.2.0 +isort==4.3.21 +javascripthon==0.11 +jdcal==1.4.1 +jedi==0.17.2 +joblib==0.15.1 +json-tricks==3.15.2 +jupyter-client==6.1.6 +jupyter-core==4.6.3 +kiwisolver==1.2.0 +lazy-object-proxy==1.4.3 +macropy3==1.1.0b2 +matplotlib==3.1.3 +mccabe==0.6.1 +more-itertools==8.4.0 +moviepy==1.0.3 +msgpack==1.0.0 +msgpack-numpy==0.4.6.post0 +nanonispy==1.0.8 +networkx==2.4 +numexpr==2.7.1 +numpy==1.18.5 +opencv-contrib-python==3.4.0.12 +openpyxl==3.0.3 +packaging==20.4 +pandas==1.0.4 +parso==0.7.1 +pexpect==4.8.0 +pickleshare==0.7.5 +Pillow==7.0.0 +pluggy==0.13.1 +proglog==0.1.9 +prompt-toolkit==3.0.6 +psutil==5.7.0 +PsychoPy==2020.1.2 +ptyprocess==0.6.0 +py==1.9.0 +pyamg==4.0.0 +pybind11==2.5.0 +pycparser==2.20 +pyglet==1.5.5 +Pygments==2.6.1 +PyInstaller==3.6 +pylint==2.5.3 +PyOpenGL==3.1.5 +pyOpenSSL==19.1.0 +pyosf==1.0.5 +pyparallel==0.2.2 +pyparsing==2.4.7 +PyQt5==5.14.2 +PyQt5-sip==12.8.0 +PyQt5-stubs==5.14.2.2 +pyserial==3.4 +pytest==5.4.3 +python-bidi==0.4.2 +python-dateutil==2.8.1 +python-gitlab==2.3.1 +pytz==2020.1 +PyWavelets==1.1.1 +PyYAML==5.3.1 +pyzmq==19.0.1 +questplus==2019.4 +requests==2.23.0 +scikit-image==0.16.2 +scikit-learn==0.22.2.post1 +scipy==1.4.1 +seaborn==0.10.1 +six==1.15.0 +smmap==3.0.4 +sounddevice==0.3.15 +SoundFile==0.10.3.post1 +tables==3.6.1 +toml==0.10.1 +tornado==6.0.4 +tqdm==4.46.1 +traitlets==4.3.3 +typed-ast==1.4.1 +urllib3==1.25.9 +vtk==8.1.2 +wcwidth==0.2.5 +wrapt==1.12.1 +xarray==0.15.1 +xlrd==1.2.0 +zipp==3.1.0 +zope.event==4.4 +zope.interface==5.1.0 diff --git a/requirements_windows.txt b/requirements_windows.txt new file mode 100644 index 0000000..443bf69 --- /dev/null +++ b/requirements_windows.txt @@ -0,0 +1,21 @@ +opencv-contrib-python== 3.4.2.16 +PyQt5~=5.14.1 +vtk~=8.1.2 +numpy~=1.18.1 +matplotlib +h5py~=2.10.0 +gwyfile~=0.2.0 +nanonispy +scikit-image +imageio +Pillow +pandas +scipy +PyYAML +scikit-learn +seaborn +networkx~=2.4 +pyamg +cvlib +tensorflow +PsychoPy~=2020.1.2 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4b7792f --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +import sys +from cx_Freeze import setup, Executable + +# Dependencies are automatically detected, but it might need fine tuning. +build_exe_options = {"includes": ["PyQt5"]} + +# GUI applications require a different base on Windows (the default is for a +# console application). +base = None +if sys.platform == "win32": + base = "Win32GUI" + +setup(name="guifoo", + version="0.1", + description="My GUI application!", + options={"build_exe": build_exe_options}, + executables=[Executable("main.py", base=base)]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/test.avi b/tests/fixtures/test.avi new file mode 100644 index 0000000..9d52f49 Binary files /dev/null and b/tests/fixtures/test.avi differ diff --git a/tests/fixtures/test.h5 b/tests/fixtures/test.h5 new file mode 100644 index 0000000..73b4366 Binary files /dev/null and b/tests/fixtures/test.h5 differ diff --git a/tests/fixtures/test.sxm b/tests/fixtures/test.sxm new file mode 100644 index 0000000..5373fdb Binary files /dev/null and b/tests/fixtures/test.sxm differ diff --git a/tests/fixtures/test.tif b/tests/fixtures/test.tif new file mode 100644 index 0000000..8a95652 Binary files /dev/null and b/tests/fixtures/test.tif differ diff --git a/tests/model_view/__init__.py b/tests/model_view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/model_view/frontend_adapter/__init__.py b/tests/model_view/frontend_adapter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/model_view/frontend_adapter/test_apply_filters.py b/tests/model_view/frontend_adapter/test_apply_filters.py new file mode 100644 index 0000000..eed396e --- /dev/null +++ b/tests/model_view/frontend_adapter/test_apply_filters.py @@ -0,0 +1,23 @@ +from importlib import reload +from model_view import frontend_adapter + +class TestApplyFilters: + @classmethod + def setup_class(cls): + reload(frontend_adapter) + file_path = "tests/fixtures/test.h5" + frontend_adapter.FrontEndAdapter.load_file(file_path) + + def test_apply_1d_filters(self): + filter_list = frontend_adapter.FrontEndAdapter.get_available_1d_filter_list() + assert filter_list != [] + for filter_index in range(len(filter_list)): + frontend_adapter.FrontEndAdapter.add_1d_filter_to_current_scan(filter_index=filter_index) + frontend_adapter.FrontEndAdapter.apply_1d_filter_current_scan() + + def test_apply_2d_filters(self): + filter_list = frontend_adapter.FrontEndAdapter.get_available_2d_filter_list() + assert filter_list != [] + for filter_index in range(len(filter_list)): + frontend_adapter.FrontEndAdapter.add_2d_filter_to_current_scan(filter_index=filter_index) + frontend_adapter.FrontEndAdapter.apply_2d_filter_current_scan() diff --git a/tests/model_view/frontend_adapter/test_feature.py b/tests/model_view/frontend_adapter/test_feature.py new file mode 100644 index 0000000..bc81069 --- /dev/null +++ b/tests/model_view/frontend_adapter/test_feature.py @@ -0,0 +1,19 @@ +from importlib import reload + +from model_view import frontend_adapter + + +class TestFeatures: + @classmethod + def setup_class(cls): + reload(frontend_adapter) + file_path = "tests/fixtures/test.h5" + frontend_adapter.FrontEndAdapter.load_file(file_path) + + def test_add_feature(self): + frontend_adapter.FrontEndAdapter.add_feature_class(feature_class="Feature", color=[0, 0, 0]) + frontend_adapter.FrontEndAdapter.add_feature_to_feature_class(feature_class_index=0) + frontend_adapter.FrontEndAdapter.add_point_to_feature(feature_class_index=0, feature_index=0, image_number=0, + position=[0, 0, 0]) + feature_dict = frontend_adapter.FrontEndAdapter.get_feature_dictionary(image_number=0, scan_index=0) + assert feature_dict == {'Feature': {0: [[0, 0, 0]]}} diff --git a/tests/model_view/frontend_adapter/test_load_from_file.py b/tests/model_view/frontend_adapter/test_load_from_file.py new file mode 100644 index 0000000..7eaa225 --- /dev/null +++ b/tests/model_view/frontend_adapter/test_load_from_file.py @@ -0,0 +1,35 @@ +from importlib import reload + +from model_view import frontend_adapter + + +class TestLoadFromFile: + + @classmethod + def setup_class(cls): + reload(frontend_adapter) + + def test_load_h5(self): + file_path = "tests/fixtures/test.h5" + loaded = frontend_adapter.FrontEndAdapter.load_file(file_path) + assert loaded is True + + def test_load_png(self): + file_path = "tests/fixtures/test.png" + loaded = frontend_adapter.FrontEndAdapter.load_file(file_path) + assert loaded is True + + def test_load_tif(self): + file_path = "tests/fixtures/test.tif" + loaded = frontend_adapter.FrontEndAdapter.load_file(file_path) + assert loaded is True + + def test_load_avi(self): + file_path = "tests/fixtures/test.avi" + loaded = frontend_adapter.FrontEndAdapter.load_file(file_path) + assert loaded is True + + # def test_load_sxm(self): + # file_path = "fixtures/test.sxm" + # loaded = frontend_adapter.FrontEndAdapter.load_file(file_path) + # assert loaded is True diff --git a/utilities/custom_logging/__init__.py b/utilities/custom_logging/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utilities/custom_logging/custom_log_handler.py b/utilities/custom_logging/custom_log_handler.py new file mode 100644 index 0000000..c738eb5 --- /dev/null +++ b/utilities/custom_logging/custom_log_handler.py @@ -0,0 +1,14 @@ +import logging + + +class CustomLogHandler(logging.StreamHandler): + def __init__(self, callback_function): + logging.StreamHandler.__init__(self) + formatter = logging.Formatter('%(levelname)-8s %(message)s \n') + self.setLevel(logging.INFO) + self.setFormatter(formatter) + self.callback_function = callback_function + + def emit(self, record): + msg = self.format(record) + self.callback_function(msg) diff --git a/utilities/decorator/__init__.py b/utilities/decorator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utilities/decorator/progress.py b/utilities/decorator/progress.py new file mode 100644 index 0000000..d41aad6 --- /dev/null +++ b/utilities/decorator/progress.py @@ -0,0 +1,24 @@ +import functools +import time +from multiprocessing.pool import ThreadPool + +from model_view.process_control.progress_handler import default_progress_handler + + +def track_process(func): + @functools.wraps(func) + def wrapper_trac_progress(*args, **kwargs): + process_started = default_progress_handler.process_started(func.__name__) + pool = ThreadPool(1) + thread_ = pool.apply_async(func, args=args, kwds=kwargs) + while not thread_.ready(): + default_progress_handler.process_events() + time.sleep(0.05) + pool.close() + pool.join() + return_value = thread_.get() + if process_started is True: + default_progress_handler.process_finished() + return return_value + + return wrapper_trac_progress diff --git a/utilities/decorator/singleton.py b/utilities/decorator/singleton.py new file mode 100644 index 0000000..1727380 --- /dev/null +++ b/utilities/decorator/singleton.py @@ -0,0 +1,14 @@ +import functools + + +def singleton(cls): + """Make a class a Singleton class (only one instance)""" + + @functools.wraps(cls) + def wrapper_singleton(*args, **kwargs): + if not wrapper_singleton.instance: + wrapper_singleton.instance = cls(*args, **kwargs) + return wrapper_singleton.instance + + wrapper_singleton.instance = None + return wrapper_singleton diff --git a/utilities/decorator/time_it_custom.py b/utilities/decorator/time_it_custom.py new file mode 100644 index 0000000..45bb10a --- /dev/null +++ b/utilities/decorator/time_it_custom.py @@ -0,0 +1,14 @@ +import functools +import time + + +def time_it(func): + @functools.wraps(func) + def wrapper_trac_progress(*args, **kwargs): + start_time = time.time() + return_value = func(*args, **kwargs) + end_time = time.time() + print("Function {} took {} seconds to execute".format(func.__name__, end_time - start_time)) + return return_value + + return wrapper_trac_progress diff --git a/utilities/images/__init__.py b/utilities/images/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utilities/images/normalizer.py b/utilities/images/normalizer.py new file mode 100644 index 0000000..330a943 --- /dev/null +++ b/utilities/images/normalizer.py @@ -0,0 +1,10 @@ +import numpy as np + + +def generate_greyscale_image(image: np.array): + image_normalize = image.copy() + image_normalize = np.nan_to_num(image_normalize) + image_normalize -= image_normalize.min() + image_normalize = image_normalize / image_normalize.max() + image_normalize *= 255 + return image_normalize diff --git a/utilities/images/numpy_array_drawer.py b/utilities/images/numpy_array_drawer.py new file mode 100644 index 0000000..5b75e10 --- /dev/null +++ b/utilities/images/numpy_array_drawer.py @@ -0,0 +1,15 @@ +import numpy as np +from PIL import Image, ImageDraw + + +def get_polygon_mask_array(input_array_shape, polygon_points, mask_type_positiv=True): + polygon_points = list(map(tuple, map(reversed, polygon_points))) + if mask_type_positiv: + img = Image.new('L', list(reversed(input_array_shape)), 0) + ImageDraw.Draw(img).polygon(polygon_points, outline=1, fill=1) + array = np.array(img) + else: + img = Image.new('L', list(reversed(input_array_shape)), 1) + ImageDraw.Draw(img).polygon(polygon_points, outline=1, fill=0) + array = np.array(img) + return array diff --git a/utilities/system_setup/__init__.py b/utilities/system_setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utilities/system_setup/requirements.py b/utilities/system_setup/requirements.py new file mode 100644 index 0000000..9276f11 --- /dev/null +++ b/utilities/system_setup/requirements.py @@ -0,0 +1,17 @@ +import logging + +import pkg_resources + +log = logging.getLogger(__name__) + + +def check_requirements(file_name=None): + if file_name is None: + file_name = "requirements.txt" + with open(file_name) as file: + try: + pkg_resources.require(file.readlines()) + return None + except Exception as e: + log.exception(e) + return str(e) diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/point_utilities.py b/utils/point_utilities.py new file mode 100644 index 0000000..90d7598 --- /dev/null +++ b/utils/point_utilities.py @@ -0,0 +1,27 @@ +import math +import operator +from functools import reduce + + +def sort_points_counter_clockwise(points): + try: + center = tuple( + map(operator.truediv, reduce(lambda x, y: map(operator.add, x, y), points), + [len(points)] * 2)) + except TypeError: + return points + sorted_points = sorted(points, key=lambda coord: (-135 - math.degrees( + math.atan2(*tuple(map(operator.sub, coord, center))[::-1]))) % 360) + return sorted_points + + +def mirror_points_image_diagonal(image_shape, point): + position = list(point) + position[0] = image_shape[0] - position[0] + return list(reversed(position)) + +def mirror_point_list_image_diagonale(image_shape, point_list): + mirror_point_list = [] + for point in point_list: + mirror_point_list.append(mirror_points_image_diagonal(image_shape, point)) + return mirror_point_list diff --git a/view/__init__.py b/view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/analyse_dialog.py b/view/analyse_dialog.py new file mode 100644 index 0000000..3993f55 --- /dev/null +++ b/view/analyse_dialog.py @@ -0,0 +1,325 @@ +import logging +import parser + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.feature_analyse_visualization.matplotlib.feature_visualization_factory import \ + FeatureAnalyseVisualizationFactory +from view.ui import ui_dialog_analyse + +log = logging.getLogger(__name__) + + +class AnalyseDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent) + log.info("Open Analyse Dialog") + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_analyse.Ui_Dialog() + self.setWindowFlags( + QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowCloseButtonHint) + self.ui_dialog.setupUi(self) + self.maximum_image_index = self.model_view.get_maximum_image_index() + self.ui_dialog.spinBox_start_image_number.setMaximum(self.maximum_image_index) + self.ui_dialog.spinBox_end_image_number.setMaximum(self.maximum_image_index) + self.ui_dialog.spinBox_end_image_number.setValue(self.maximum_image_index) + self.ui_dialog.spinBox_start_image_number.editingFinished.connect(self.handle_start_image_number_changed) + self.ui_dialog.spinBox_end_image_number.editingFinished.connect(self.handle_end_image_number_changed) + self.initilize_selected_feature_dict() + self.update_feature_class_list_widget() + self.set_up_visualization_combobox() + self.generate_analyse_render_window() + self.ui_dialog.pushButton_feature_class_check_all.clicked.connect(self.handle_feature_class_check_all) + self.ui_dialog.pushButton_feature_class_uncheck_all.clicked.connect(self.handle_feature_class_uncheck_all) + self.ui_dialog.pushButton_feature_class_invert.clicked.connect(self.handle_feature_class_invert) + self.ui_dialog.pushButton_feature_list_check_all.clicked.connect(self.handle_feature_list_check_all) + self.ui_dialog.pushButton_feature_list_uncheck_all.clicked.connect(self.handle_feature_list_uncheck_all) + self.ui_dialog.pushButton_feature_list_invert.clicked.connect(self.handle_feature_list_invert) + self.ui_dialog.listWidget_feature_class.currentItemChanged.connect(self.handle_change_feature_class) + self.ui_dialog.listWidget_equal_jump_thresholds.currentItemChanged.connect(self.handle_equal_jump_list_widget) + self.ui_dialog.listWidget_feature.currentItemChanged.connect(self.handle_change_feature_index) + self.ui_dialog.checkBox_count_nan_jumps.clicked.connect(self.handle_check_count_nan_jumps) + self.ui_dialog.pushButton_eqaul_number_of_jumps_threshold.clicked.connect( + self.handle_equal_number_of_jumps_threshold) + self.ui_dialog.comboBox_analyse_window.currentIndexChanged.connect(self.generate_analyse_render_window) + self.plot.left_click_position.connect(self.handle_left_click) + self.ui_dialog.lineEdit_jump_threshold.returnPressed.connect(self.handle_threshold_changed) + self.equal_jump_threshold_list = [] + self.update_information_window() + + def get_feature_list_by_class_index(self, class_index): + try: + return self.selected_feature_dict[class_index] + except KeyError: + self.selected_feature_dict[class_index] = [] + return self.selected_feature_dict[class_index] + + def set_up_visualization_combobox(self): + feature_visualization_items = FeatureAnalyseVisualizationFactory.analye_visualization_dict.keys() + self.ui_dialog.comboBox_analyse_window.clear() + self.ui_dialog.comboBox_analyse_window.addItems(feature_visualization_items) + + def handle_change_feature_class(self): + self.update_feature_list_widget() + self.update_information_window() + + def handle_change_feature_index(self): + self.update_analyse_render_window() + + def handle_left_click(self, position_data): + if self.ui_dialog.comboBox_analyse_window.currentText() == "Feature Class Jump Distances Plot": + self.ui_dialog.lineEdit_jump_threshold.setText("{:.4g}".format(position_data[1])) + self.update_analyse_render_window() + self.update_information_window() + + def generate_analyse_render_window(self): + visualization_name = self.ui_dialog.comboBox_analyse_window.currentText() + self.analyse_widget = self.ui_dialog.widget_analyse_window + self.plot = FeatureAnalyseVisualizationFactory.get_feature_visualization(visualization_name, self.model_view, + self.analyse_widget) + self.plot.left_click_position.connect(self.handle_left_click) + self.update_analyse_render_window() + + def get_thresold(self): + threshold_str = self.ui_dialog.lineEdit_jump_threshold.text() + if threshold_str != "": + try: + threshold = eval(parser.expr(threshold_str).compile()) + except SyntaxError as e: + log.info(e.text) + threshold = 0 + else: + threshold = 0 + return threshold + + def update_analyse_render_window(self): + feature_class_index = self.ui_dialog.listWidget_feature_class.currentRow() + feature_index = self.ui_dialog.listWidget_feature.currentRow() + threshold = self.get_thresold() + selected_feature_dict = self.get_back_end_selected_feature_dict() + if feature_index >= 0 and feature_class_index >= 0: + self.plot.render(self.get_image_number_range(), feature_class_index, feature_index, threshold, + self.selected_feature_classes, + selected_feature_dict) + else: + self.plot.clear() + + def handle_threshold_changed(self): + self.update_analyse_render_window() + self.update_information_window() + + def initilize_selected_feature_dict(self, selected_feature_classes=None, selected_feature_dict=None): + feature_class_list = self.model_view.get_feature_classes_list() + if selected_feature_classes is None: + self.selected_feature_classes = feature_class_list + else: + self.selected_feature_classes = selected_feature_classes + if selected_feature_dict is None: + self.selected_feature_dict = {} + for feature_class_index, feature_class_name in enumerate(feature_class_list): + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=feature_class_index) + self.selected_feature_dict[feature_class_name] = feature_list + + def update_feature_class_list_widget(self): + feature_class_list = self.model_view.get_feature_classes_list() + self.ui_dialog.listWidget_feature_class.clear() + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature_class, + item_list=feature_class_list, + selected_item_list=self.selected_feature_classes, + check_box_call_back_function=self.get_selected_feature_classes) + self.ui_dialog.listWidget_feature_class.setCurrentRow(0) + self.update_feature_list_widget() + + def update_feature_list_widget(self): + current_feature_class_index = self.ui_dialog.listWidget_feature_class.currentRow() + self.ui_dialog.listWidget_feature.clear() + if current_feature_class_index >= 0: + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=current_feature_class_index) + current_feature_class_name = self.get_current_feature_class_name() + selected_item_list = self.get_feature_list_by_class_index(current_feature_class_name) + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature, + item_list=feature_list, selected_item_list=selected_item_list, + check_box_call_back_function=self.set_selected_features_in_feature_dict) + self.ui_dialog.listWidget_feature.setCurrentRow(0) + + def get_current_feature_class_name(self): + list_widget = self.ui_dialog.listWidget_feature_class + current_index = list_widget.currentRow() + list_widget_item = list_widget.item(current_index) + label = list_widget.itemWidget(list_widget_item).children()[1] + return label.text() + + def get_back_end_selected_feature_dict(self): + selected_feature_dict = {} + for class_name, feature_list in self.selected_feature_dict.items(): + selected_feature_dict[class_name] = [int(feature) for feature in feature_list] + return selected_feature_dict + + def update_information_window(self): + self.ui_dialog.textBrowser_feature_analyse.clear() + threshold = self.get_thresold() + selected_feature_dict = self.get_back_end_selected_feature_dict() + count_nan_jumps = self.ui_dialog.checkBox_count_nan_jumps.isChecked() + selected_feature_classes = self.selected_feature_classes + image_range = self.get_image_number_range() + jump_frequency, jump_frequency_normed, number_of_jumps = self.model_view.get_jump_informations(image_range, + selected_feature_classes, + selected_feature_dict, + threshold=threshold, + count_nan_jumps=count_nan_jumps) + average_jump_distance = self.model_view.get_average_jump_distance(image_range, selected_feature_classes, + selected_feature_dict, threshold=threshold) + number_of_jumps_str = "Number of Jumps: {} \n".format(number_of_jumps) + average_jump_distance_str = "Average Jump Distance: {:.4g}\n".format(average_jump_distance) + jump_frequency_str = "Jump Frequency: {} Hz \n".format(str(jump_frequency)) + jump_frequency_normd_str = "Jump Frequency for Jumps inside of Scan: {} Hz\n".format(str(jump_frequency_normed)) + self.ui_dialog.textBrowser_feature_analyse.setText( + number_of_jumps_str + average_jump_distance_str + jump_frequency_str + jump_frequency_normd_str) + + def generate_feature_list_with_checkboxes(self, list_widget, item_list, selected_item_list, + check_box_call_back_function=None): + for item in item_list: + # Create widget + # Add widget to QListWidget funList + layout = QtWidgets.QHBoxLayout() + label = QtWidgets.QLabel(item) + check_box = QtWidgets.QCheckBox() + if check_box_call_back_function is not None: + check_box.clicked.connect(check_box_call_back_function) + if item in selected_item_list: + check_box.setChecked(True) + layout.addWidget(label) + layout.addWidget(check_box) + layout.addStretch() + list_widget_item = QtWidgets.QListWidgetItem() + widget = QtWidgets.QWidget() + layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + widget.setLayout(layout) + list_widget_item.setSizeHint(widget.sizeHint()) + list_widget.addItem(list_widget_item) + list_widget.setItemWidget(list_widget_item, widget) + + def get_selected_items_by_list_widget(self, list_widget): + item_list = [] + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + label = list_widget.itemWidget(list_widget_item).children()[1] + if check_box.isChecked(): + item_list.append(label.text()) + return item_list + + def update_ui(self): + if self.ui_dialog.spinBox_start_image_number.value() < self.ui_dialog.spinBox_end_image_number.value(): + self.update_analyse_render_window() + self.update_information_window() + + def set_selected_features_in_feature_dict(self): + feature_class_name = self.get_current_feature_class_name() + selected_features = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature) + self.selected_feature_dict[feature_class_name] = selected_features + self.update_information_window() + self.update_analyse_render_window() + + def get_selected_feature_classes(self): + self.selected_feature_classes = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature_class) + self.update_information_window() + self.update_analyse_render_window() + + def handle_feature_class_check_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_check_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_uncheck_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_invert(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_invert(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_list_check_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_check_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_uncheck_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_invert(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_invert(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def list_widget_check_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(True) + + def list_widget_uncheck_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(False) + + def list_widget_invert(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(not check_box.isChecked()) + + def handle_equal_number_of_jumps_threshold(self): + selected_feature_dict = self.get_back_end_selected_feature_dict() + thresholds = self.model_view.get_threshold_for_equal_jumps_in_selected_feature_classes( + image_range=self.get_image_number_range(), + selected_feature_classes=self.selected_feature_classes, + selected_feature_class_dict=selected_feature_dict) + self.equal_jump_threshold_list = [] + for number_of_jumps, threshold in thresholds.items(): + self.ui_dialog.listWidget_equal_jump_thresholds.addItem( + "Number of Jumps {}: {}".format(number_of_jumps, threshold)) + self.equal_jump_threshold_list.append([number_of_jumps, threshold]) + + def handle_check_count_nan_jumps(self): + self.update_information_window() + + def handle_equal_jump_list_widget(self): + current_index = self.ui_dialog.listWidget_equal_jump_thresholds.currentRow() + number_of_jumps, threshold = self.equal_jump_threshold_list[current_index] + self.ui_dialog.lineEdit_jump_threshold.setText(str(threshold)) + self.update_ui() + + def handle_start_image_number_changed(self): + image_range = self.get_image_number_range() + if image_range[0] >= image_range[1] and image_range[0] < self.maximum_image_index: + self.ui_dialog.spinBox_end_image_number.blockSignals(True) + self.ui_dialog.spinBox_end_image_number.setValue(image_range[0] + 1) + self.ui_dialog.spinBox_end_image_number.blockSignals(False) + + self.update_ui() + + def handle_end_image_number_changed(self): + image_range = self.get_image_number_range() + if image_range[1] <= image_range[0] and image_range[1] > 0: + self.ui_dialog.spinBox_start_image_number.blockSignals(True) + self.ui_dialog.spinBox_start_image_number.setValue(image_range[1] - 1) + self.ui_dialog.spinBox_start_image_number.blockSignals(False) + self.update_ui() + + def get_image_number_range(self): + return [self.ui_dialog.spinBox_start_image_number.value(), self.ui_dialog.spinBox_end_image_number.value()] diff --git a/view/average_image_dialog.py b/view/average_image_dialog.py new file mode 100644 index 0000000..f124183 --- /dev/null +++ b/view/average_image_dialog.py @@ -0,0 +1,53 @@ + + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.average_image_visualization.matplotlib import average_image_visualization +from view.ui import ui_dialog_average_image + + +class AverageImageDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_average_image.Ui_AverageImageDialog() + self.ui_dialog.setupUi(self) + self.set_up_ui_fields() + max_image_index = self.model_view.get_maximum_image_index() + self.ui_dialog.spinBox_image_number.setMaximum(max_image_index) + self.ui_dialog.spinBox_image_number.valueChanged.connect(self.handle_image_number_changed) + self.ui_dialog.comboBox_image_comparison_function.currentTextChanged.connect(self.handle_combo_box_changed) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.render_window = average_image_visualization.AverageImageVisualization( + widget=self.ui_dialog.widget_render_window, + model_view=self.model_view) + self.update_render_window() + + def set_up_ui_fields(self): + self._set_up_image_ui() + self._set_up_function_combo_box() + + def _set_up_image_ui(self): + pass + + def _set_up_function_combo_box(self): + image_functions = ["Maximum", "Minimum", "Average"] + self.ui_dialog.comboBox_image_comparison_function.addItems(image_functions) + + def update_render_window(self): + image_number = self.ui_dialog.spinBox_image_number.value() + image_function = self.ui_dialog.comboBox_image_comparison_function.currentText() + self.render_window.render(image_number=image_number, image_function=image_function) + + def handle_combo_box_changed(self): + self.update_render_window() + + def handle_image_number_changed(self): + self.image_number = self.ui_dialog.spinBox_image_number.value() + self.update_render_window() + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) + diff --git a/view/average_image_visualization/__init__.py b/view/average_image_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/average_image_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/average_image_visualization/matplotlib/__init__.py b/view/average_image_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/average_image_visualization/matplotlib/__init__.py @@ -0,0 +1 @@ + diff --git a/view/average_image_visualization/matplotlib/average_image_visualization.py b/view/average_image_visualization/matplotlib/average_image_visualization.py new file mode 100644 index 0000000..eadc413 --- /dev/null +++ b/view/average_image_visualization/matplotlib/average_image_visualization.py @@ -0,0 +1,271 @@ + + +import math + +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.collections import EllipseCollection +from matplotlib.figure import Figure +from matplotlib.patches import RegularPolygon +from skimage.feature import blob_log + +from model_view import frontend_adapter + + +class AverageImageVisualization(QtCore.QObject): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + lasso_selection = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + QtCore.QObject.__init__(self) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.set_up_matplotlib_interactor() + self.hexagon_point_list = [] + self.axes = self.figure.add_subplot(111) + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, image_number, image_function): + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.render_image(image_number, image_function) + self.update() + + def render_image(self, image_number, image_function): + image = self.get_image_by_image_function(image_function) + self.image = image + # image = self.get_feature_list(image) + self.render_hexagon_grid(image) + step_size = self.model_view.get_step_size() + self.axes.imshow(image, cmap="gray", zorder=0, + extent=[0, len(image[0]) * step_size[0], len(image) * step_size[1], 0]) + + def get_image_by_image_function(self, image_function): + if image_function == "Minimum": + image = self.model_view.get_min_image() + elif image_function == "Maximum": + image = self.model_view.get_max_image() + elif image_function == "Average": + image = self.model_view.get_2d_average_image() + return image + + def generate_greyscale_image(self, image: np.array): + image_normalize = image.copy() + image_normalize -= np.nanmin(image_normalize) + image_normalize = np.nan_to_num(image_normalize) + + image_normalize = image_normalize / image_normalize.max() + image_normalize *= 255 + image_normalize = image_normalize.astype(int) + return image_normalize + + def reset_render_window(self): + self.update() + + def get_feature_list(self, image): + parameters = {"min_sigma": 2, + "max_sigma": 2, + "num_sigma": 10, + "threshold": 0, + "overlap": 0, } + min_sigma = parameters["min_sigma"] + max_sigma = parameters["max_sigma"] + num_sigma = parameters["num_sigma"] + threshold = parameters["threshold"] + overlap = parameters["overlap"] + image = image.copy() + image_min = np.nanmin(image) + image_max = np.nanmax(image) + + normalize_image = image - image_min + normalize_image = normalize_image / image_max + normalize_image *= 255 + # normalize_image = np.nan_to_num(normalize_image) + normalize_image = self.__plane_flatten(normalize_image) + blobs_log = blob_log(normalize_image, min_sigma=min_sigma, max_sigma=max_sigma, + num_sigma=num_sigma, overlap=overlap) + blobs_log[:, 2] = blobs_log[:, 2] * math.sqrt(2) + # swapping first and second column, because they are in wrong order + blobs_log[:, [0, 1]] = blobs_log[:, [1, 0]] + step_size = self.model_view.get_step_size() + blob_position = [[float(blob[0]) * step_size[0], float(blob[1]) * step_size[1]] for blob in + blobs_log] + blobs_log_list = [[float(blob[0]), float(blob[1]), float(blob[2])] for blob in + blobs_log] + feature_radius_list = [float(blob[2]) * step_size[0] for blob in blobs_log] + circle_collection = EllipseCollection(feature_radius_list, feature_radius_list, + np.zeros_like(feature_radius_list), + offsets=blob_position, units='x', + transOffset=self.axes.transData) + self.axes.add_collection(circle_collection) + return normalize_image + + def __get_mean_column_slope(self, array): + """ Return the average column slope. + + This function fits a line through the values of each column and return the + average slope. + + Parameters + ---------- + array : ndarry with ndim = 2 + A two dimensional array with rows and columns. + + Returns + ------- + float + The average column slope + + """ + + number_rows, number_cols = array.shape + + row_vector = np.arange(number_rows) + + col_slopes = np.empty(number_cols) + col_slopes[:] = np.nan + + for i, col in enumerate(array.T): + idx = np.isfinite(col) + finite_col = col[idx] + if finite_col.size: + slope = np.poly1d(np.polyfit(row_vector[idx], finite_col, 1))[1] + col_slopes[i] = slope + + mean_column_slope = col_slopes[~np.isnan(col_slopes)].mean() + + return mean_column_slope + + def __plane_flatten(self, image): + """Determine fit plane and subtract it from image data. + + This function gets the mean flattening plane of the image. + The image is corrected by subtracting this offsets from the image which + results from a tilted sample surface or other measuring artefacts. + + Parameters + ---------- + image : array_like + Greyscale image MxN. + + Returns + ------- + ndarray + Image after plane flattening. + ndarray + Plane. + + Notes + ----- + For a similar solution see: [#]. + + References + ---------- + .. [#] https://pypi.org/project/SPIEPy/ + """ + number_rows, number_cols = image.shape + row_vector = np.arange(number_rows) + col_vector = np.arange(number_cols) + + mean_col_slope = self.__get_mean_column_slope(image) + mean_row_slope = self.__get_mean_column_slope(image.T) + + row, col = np.meshgrid(col_vector, row_vector) + + # add (superpose) planes to get the overall tilted plane + image_plane = row * mean_row_slope + col * mean_col_slope + + # subtract plane from image + flattened_image = image - image_plane + return flattened_image + + @staticmethod + def perpendicular(a): + b = np.empty_like(a) + b[0] = -a[1] + b[1] = a[0] + return b + + @staticmethod + def normalize(a): + a = np.array(a) + return a / np.linalg.norm(a) + + def render_hexagon_grid(self, image): + """ + returns an array of Points describing hexagons centers that are inside the given bounding_box + :param bbox: The containing bounding box. The bbox coordinate should be in Webmercator. + :param side: The size of the hexagons' + :return: The hexagon grid + """ + if len(self.hexagon_point_list) < 2: + return + step_size = self.model_view.get_step_size() + point_list = np.asarray(self.hexagon_point_list[:2]) + vector = point_list[1] - point_list[0] + horizontal_distance = math.hypot(vector[0], vector[1]) + radius = horizontal_distance / math.sqrt(3) + vertical_distance = radius * 3 / 2 + mid_point = vector / 2 + point_list[0] + vector_perpendicular = self.normalize(self.perpendicular(vector)) + point_3 = mid_point + vector_perpendicular * vertical_distance + vector_2 = point_list[0] - point_3 + number_of_hexagon = int(image.shape[0] * step_size[0] / radius) + hcoord = [] + vcoord = [] + for sign_i in [-1, 1]: + for sign_j in [-1, 1]: + for j in range(number_of_hexagon): + vector_1_offset_v = point_list[0][0] + j * sign_j * vector_2[0] + vector_1_offset_h = point_list[0][1] + j * sign_j * vector_2[1] + for i in range(number_of_hexagon): + v_coord = i * sign_i * vector[0] + vector_1_offset_v + h_coord = i * sign_i * vector[1] + vector_1_offset_h + hcoord.append(h_coord) + vcoord.append(v_coord) + coords = np.array([hcoord, vcoord]).T + inside_mask = (coords[:, 0] < (image.shape[0] + radius) * step_size[0]) & (coords[:, 0] >= -radius) | ( + coords[:, 1] < (image.shape[0] + radius) * step_size[1]) & (coords[:, 1] >= -radius) + coords = coords[inside_mask, :] + coords = np.unique(coords, axis=0) + # Add some coloured hexagons + angle = np.arctan(vector[0] / vector[1]) + for x, y in coords: + hex = RegularPolygon((x, y), numVertices=6, radius=radius, + orientation=angle, alpha=0.2, edgecolor='k') + self.axes.add_patch(hex) + + self.canvas.draw() + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.hexagon_point_list.append([event.ydata, event.xdata]) + self.render_hexagon_grid(self.image) + elif event.button == 3: + if len(self.hexagon_point_list) > 1: + self.hexagon_point_list.pop() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) diff --git a/view/color_map_picker_dialog.py b/view/color_map_picker_dialog.py new file mode 100644 index 0000000..048c688 --- /dev/null +++ b/view/color_map_picker_dialog.py @@ -0,0 +1,133 @@ +import copy + +import matplotlib +import matplotlib.pyplot +from PyQt5 import QtWidgets, QtCore +from PyQt5.QtGui import QColor +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + +from model_view import frontend_adapter +from view.saasmi_visualization import saasmi_utilities +from view.ui import ui_dialog_color_map_picker + + +class ColorMapDialog(QtWidgets.QDialog): + def __init__(self, model_view, color_range=None, parent=None, color_map_dict=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_color_map_picker.Ui_Dialog() + self.color_range = color_range or [0, 100] + self.ui_dialog.setupUi(self) + self.set_up_cmap_combo_box() + self.figure = Figure() + self.figure.set_tight_layout(True) + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) + self.canvas.setFocus() + self.canvas.draw() + widget = self.ui_dialog.widget_color_bar + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.set_up_ui_fields(color_map_dict) + self.update_color_bar() + self.figure.tight_layout() + widget.setLayout(self.vl) + self.ui_dialog.comboBox_color_map.currentIndexChanged.connect(self.update_color_bar) + self.ui_dialog.doubleSpinBox_color_value_max.valueChanged.connect(self.update_color_bar) + self.ui_dialog.doubleSpinBox_color_value_min.valueChanged.connect(self.update_color_bar) + self.ui_dialog.checkBox_use_bound_color.stateChanged.connect(self.update_color_bar) + self.ui_dialog.pushButton_color_lower_bounds.clicked.connect(self.handle_lower_color_button) + self.ui_dialog.pushButton_color_upper_bounds.clicked.connect(self.handle_upper_color_button) + self.ui_dialog.pushButton_reset_color_range.clicked.connect(self.handle_reset_color_range) + + def set_up_ui_fields(self, color_map_dict): + if color_map_dict is None: + self.color_lower_bound = [0, 0, 0] + self.color_upper_bound = [0, 0, 0] + else: + self.color_lower_bound = color_map_dict["color_lower_bound"] + self.color_upper_bound = color_map_dict["color_upper_bound"] + self.color_map_name = color_map_dict["color_map_name"] + self.ui_dialog.comboBox_color_map.setCurrentText(self.color_map_name) + self.ui_dialog.doubleSpinBox_color_value_min.setValue(color_map_dict["value_min"]) + self.ui_dialog.doubleSpinBox_color_value_max.setValue(color_map_dict["value_max"]) + self.ui_dialog.checkBox_use_bound_color.setChecked(color_map_dict["use_bound_colors"]) + self._set_button_color(self.ui_dialog.pushButton_color_upper_bounds, self.color_upper_bound) + self._set_button_color(self.ui_dialog.pushButton_color_lower_bounds, self.color_lower_bound) + + def _get_color_from_color_dialog(self): + color_dialog = QtWidgets.QColorDialog() + color = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog).getRgb() + color = color[:3] + color = [c / 255 for c in color] + return color + + def handle_upper_color_button(self): + self.color_upper_bound = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_color_upper_bounds, self.color_upper_bound) + self.update_color_bar() + + def handle_lower_color_button(self): + self.color_lower_bound = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_color_lower_bounds, self.color_lower_bound) + self.update_color_bar() + + def _set_button_color(self, button, color): + button.setStyleSheet( + "background-color : rgb({r},{g},{b})".format(r=color[0] * 255, g=color[1] * 255, b=color[2] * 255)) + + def update_color_bar(self): + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.color_map_name = self.ui_dialog.comboBox_color_map.currentText() + color_min = self.ui_dialog.doubleSpinBox_color_value_min.value() + color_max = self.ui_dialog.doubleSpinBox_color_value_max.value() + self.color_map = copy.copy(matplotlib.pyplot.get_cmap(self.color_map_name)) + if self.ui_dialog.checkBox_use_bound_color.isChecked(): + self.color_map.set_under(self.color_lower_bound) + self.color_map.set_over(self.color_upper_bound) + self.color_normalizer = matplotlib.colors.Normalize(vmin=color_min, vmax=color_max) + cb1 = matplotlib.colorbar.ColorbarBase(self.axes, cmap=self.color_map, + norm=self.color_normalizer, + orientation='horizontal', extend='both') + self.figure.set_tight_layout(True) + self.canvas.draw() + + def accept(self) -> None: + value_min = self.ui_dialog.doubleSpinBox_color_value_min.value() + value_max = self.ui_dialog.doubleSpinBox_color_value_max.value() + use_bound_color = self.ui_dialog.checkBox_use_bound_color.isChecked() + color_upper_bound = self.color_upper_bound + color_lower_bound = self.color_lower_bound + color_map_name = self.color_map_name + self.color_map_dict = {"value_min": value_min, + "value_max": value_max, + "use_bound_colors": use_bound_color, + "color_lower_bound": color_lower_bound, + "color_upper_bound": color_upper_bound, + "color_map_name": color_map_name + } + self.done(QtWidgets.QDialog.Accepted) + + def handle_reset_color_range(self): + self.ui_dialog.doubleSpinBox_color_value_min.setValue(self.color_range[0]) + self.ui_dialog.doubleSpinBox_color_value_max.setValue(self.color_range[1]) + self.update_color_bar() + + def set_up_cmap_combo_box(self): + color_maps_ordered_dict = saasmi_utilities.color_map_ordered_dict + for color_map_category, color_map_name_list in color_maps_ordered_dict.items(): + self.ui_dialog.comboBox_color_map.addItem(color_map_category) + category_item = self.ui_dialog.comboBox_color_map.model().item( + self.ui_dialog.comboBox_color_map.count() - 1) + category_item.setSelectable(False) + category_item.setForeground(QColor('black')) + category_item.setBackground(QColor('darkGray')) + self.ui_dialog.comboBox_color_map.addItems(color_map_name_list) diff --git a/view/contrast_dialog.py b/view/contrast_dialog.py new file mode 100644 index 0000000..9217218 --- /dev/null +++ b/view/contrast_dialog.py @@ -0,0 +1,134 @@ + + +import numpy as np +from PyQt5 import QtWidgets, QtCore +from scipy.stats import percentileofscore + +from model_view import frontend_adapter +from view.contrast_picker_visualization.matplotlib import contrast_picker_visualization +from view.image_visualization.vtk import qt_vtk_render_window +from view.ui import ui_dialog_contrast + + +class ContrastDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_contrast.Ui_ContrastDialog() + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.ui_dialog.setupUi(self) + self.set_contrast_ui() + self.ui_dialog.radioButton_contrast_absolute.clicked.connect(self.change_contrast) + self.ui_dialog.radioButton_contrast_percentile.clicked.connect(self.change_contrast) + self.ui_dialog.radioButton_full_range.clicked.connect(self.change_contrast) + self.ui_dialog.doubleSpinBox_contrast_percentile_max.valueChanged.connect(self.change_contrast) + self.ui_dialog.doubleSpinBox_contrast_percentile_min.valueChanged.connect(self.change_contrast) + self.ui_dialog.doubleSpinBox_contrast_absolute_min.valueChanged.connect(self.change_contrast) + self.ui_dialog.doubleSpinBox_contrast_absolute_max.valueChanged.connect(self.change_contrast) + self.generate_render_windows() + self.contrast_picker_window.left_click_position.connect(self.handle_contrast_picker_left_click) + self.contrast_picker_window.right_click_position.connect(self.handle_contrast_picker_right_click) + + def generate_render_windows(self): + widget_contrast_picker = self.ui_dialog.widget_image_histogram + self.contrast_picker_window = contrast_picker_visualization.ContrastPickerVisualization(widget_contrast_picker, + self.model_view) + widget_image = self.ui_dialog.widget_image_render + self.image_render_window = qt_vtk_render_window.CustomQtVTKRenderWindow(widget=widget_image, + model_view=self.model_view) + self.update_render_windows() + self.image_render_window.reset_render_camera() + + def update_render_windows(self): + self.update_image_render_window() + self.update_contrast_picker_window() + + def update_contrast_picker_window(self): + if self.ui_dialog.radioButton_contrast_absolute.isChecked(): + method = "absolute" + contrast_min = self.ui_dialog.doubleSpinBox_contrast_absolute_min.value() + contrast_max = self.ui_dialog.doubleSpinBox_contrast_absolute_max.value() + elif self.ui_dialog.radioButton_contrast_percentile.isChecked(): + method = "percentile" + contrast_min = self.ui_dialog.doubleSpinBox_contrast_percentile_min.value() + contrast_max = self.ui_dialog.doubleSpinBox_contrast_percentile_max.value() + elif self.ui_dialog.radioButton_full_range.isChecked(): + method = None + contrast_min, contrast_max = None, None + self.contrast_picker_window.render(self.image_number, method=method, min_value=contrast_min, + max_value=contrast_max) + + def update_image_render_window(self): + self.image_render_window.render(scan_index=None, image_number=self.image_number, show_features=False) + + def change_contrast(self): + self.method = None + if self.ui_dialog.radioButton_contrast_absolute.isChecked(): + method = "absolute" + contrast_min = self.ui_dialog.doubleSpinBox_contrast_absolute_min.value() + contrast_max = self.ui_dialog.doubleSpinBox_contrast_absolute_max.value() + elif self.ui_dialog.radioButton_contrast_percentile.isChecked(): + method = "percentile" + contrast_min = self.ui_dialog.doubleSpinBox_contrast_percentile_min.value() + contrast_max = self.ui_dialog.doubleSpinBox_contrast_percentile_max.value() + elif self.ui_dialog.radioButton_full_range.isChecked(): + method = None + contrast_min, contrast_max = None, None + + self.model_view.change_contrast_for_current_scan(method=method, contrast_min=contrast_min, + contrast_max=contrast_max) + self.update_render_windows() + + def set_contrast_ui(self, ): + visualization_settings = self.model_view.get_visualization_settings() + try: + contrast_method = visualization_settings['contrast_method'] + contrast_parameters = visualization_settings['contrast_parameters'] + except AttributeError: + contrast_method = None + contrast_parameters = None + if contrast_method == 'percentile': + self.ui_dialog.radioButton_contrast_percentile.setChecked(True) + self.ui_dialog.doubleSpinBox_contrast_percentile_min.setValue(contrast_parameters[0]) + self.ui_dialog.doubleSpinBox_contrast_percentile_max.setValue(contrast_parameters[1]) + elif contrast_method == 'absolute': + self.ui_dialog.radioButton_contrast_absolute.setChecked(True) + self.ui_dialog.doubleSpinBox_contrast_absolute_min.setValue(contrast_parameters[0]) + self.ui_dialog.doubleSpinBox_contrast_absolute_max.setValue(contrast_parameters[1]) + else: + self.ui_dialog.radioButton_full_range.setChecked(True) + + def handle_contrast_picker_left_click(self, position): + if self.ui_dialog.radioButton_contrast_percentile.isChecked(): + self.set_percentile(position, True) + else: + self.set_absolute(position, True) + + def handle_contrast_picker_right_click(self, position): + if self.ui_dialog.radioButton_contrast_percentile.isChecked(): + self.set_percentile(position, False) + else: + self.set_absolute(position, False) + + def set_percentile(self, position, is_left_click): + percentile_value = self._get_percentile_value(position[0]) + if is_left_click is True: + self.ui_dialog.doubleSpinBox_contrast_percentile_min.setValue(percentile_value) + else: + self.ui_dialog.doubleSpinBox_contrast_percentile_max.setValue(percentile_value) + + def _get_percentile_value(self, value): + image = self.model_view.get_2d_image_by_number(self.image_number) + percentile = percentileofscore(image[~np.isnan(image)], value) + return percentile + + def set_absolute(self, position, is_left_click): + if is_left_click is True: + self.ui_dialog.doubleSpinBox_contrast_absolute_min.setValue(position[0]) + else: + self.ui_dialog.doubleSpinBox_contrast_absolute_max.setValue(position[0]) + self.ui_dialog.radioButton_contrast_absolute.setChecked(True) + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/contrast_picker_visualization/__init__.py b/view/contrast_picker_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/contrast_picker_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/contrast_picker_visualization/matplotlib/__init__.py b/view/contrast_picker_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/contrast_picker_visualization/matplotlib/__init__.py @@ -0,0 +1 @@ + diff --git a/view/contrast_picker_visualization/matplotlib/contrast_picker_visualization.py b/view/contrast_picker_visualization/matplotlib/contrast_picker_visualization.py new file mode 100644 index 0000000..89da47c --- /dev/null +++ b/view/contrast_picker_visualization/matplotlib/contrast_picker_visualization.py @@ -0,0 +1,78 @@ + + +import numpy as np +from PyQt5 import QtCore, QtWidgets +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + + +class ContrastPickerVisualization(QtCore.QObject): + left_click_position = QtCore.pyqtSignal(list) + right_click_position = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + self.axes = self.figure.add_subplot(111) + widget.setLayout(self.vl) + self.canvas.mpl_connect("key_press_event", self.key_pressed) + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + self.area_polygon = None + self.patches = None + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, image_number, method=None, min_value=None, max_value=None): + self.render_histogram(image_number, method, min_value, max_value) + self.render_selected_area() + self.update() + + def render_histogram(self, image_number, method, min_value, max_value): + if self.area_polygon is not None: + try: + self.area_polygon.remove() + except ValueError: + pass + image = self.model_view.get_2d_image_by_number(image_number) + if method == "percentile": + min_value, max_value = np.percentile(image[~np.isnan(image)], (min_value, max_value)) + if self.patches is None: + _, _, self.patches = self.axes.hist(image[~np.isnan(image)], bins=1000) + if min_value is not None and max_value is not None: + self.area_polygon = self.axes.axvspan(min_value, max_value, color='red', alpha=0.5) + + def render_selected_area(self): + pass + + def reset_render_window(self): + self.update() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.xdata, event.ydata]) + elif event.button == 3: + self.right_click_position.emit([event.xdata, event.ydata]) + + def key_pressed(self, event): + pass diff --git a/view/density_dialog.py b/view/density_dialog.py new file mode 100644 index 0000000..c129c6d --- /dev/null +++ b/view/density_dialog.py @@ -0,0 +1,125 @@ +import logging + +import matplotlib +import matplotlib.pyplot as plt +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.density_visualization.matplotlib.density_render_window import DensityRenderWindow +from view.ui import ui_dialog_density + +log = logging.getLogger(__name__) + + +class DensityDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_density.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.set_up_ui_fields() + self.render_window = DensityRenderWindow(widget=self.ui_dialog.widget_render_window, + model_view=self.model_view) + self.ui_dialog.comboBox_color_map.currentTextChanged.connect(self.handle_image_color_map_changed) + self.ui_dialog.pushButton_nan_color.clicked.connect(self.handle_nan_color_button) + self.ui_dialog.spinBox_image_number.valueChanged.connect(self.handle_image_number_changed) + self.ui_dialog.checkBox_logarithmic_color.clicked.connect(self.update_render_window) + self.ui_dialog.radioButton_default.clicked.connect(self.update_render_window) + self.ui_dialog.radioButton_absolute.clicked.connect(self.update_render_window) + self.ui_dialog.spinBox_absolute_min.valueChanged.connect(self.update_render_window) + self.ui_dialog.spinBox_absolute_max.valueChanged.connect(self.update_render_window) + self.ui_dialog.radioButton_percentile.clicked.connect(self.update_render_window) + self.ui_dialog.spinBox_percentile_min.valueChanged.connect(self.update_render_window) + self.ui_dialog.spinBox_percentile_max.valueChanged.connect(self.update_render_window) + self.update_render_window() + + def set_up_ui_fields(self): + self._set_up_image_ui() + self._set_up_image_spin_box() + self._set_up_color_ui() + + def _set_up_color_ui(self): + pass + + def _set_up_image_spin_box(self): + max_image_number = self.model_view.get_maximum_image_index() + self.ui_dialog.spinBox_image_number.setMaximum(max_image_number) + self.ui_dialog.spinBox_image_number.setValue(self.image_number) + + def _set_up_image_ui(self): + color_maps = matplotlib.pyplot.colormaps() + self.ui_dialog.comboBox_color_map.addItems(color_maps) + image_color_map = self.model_view.get_config_by_key_sub_key("density_dialog", "image_color_map") + self.nan_color = self.model_view.get_config_by_key_sub_key("density_dialog", "nan_color") + self.ui_dialog.comboBox_color_map.setCurrentText(image_color_map) + self._set_button_color(self.ui_dialog.pushButton_nan_color, self.nan_color) + + def _get_color_from_color_dialog(self): + color_dialog = QtWidgets.QColorDialog() + color = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog).getRgb() + color = color[:3] + color = [c / 255 for c in color] + return color + + def handle_nan_color_button(self): + self.nan_color = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_nan_color, self.nan_color) + self.update_render_window() + + def _set_button_color(self, button, color): + button.setStyleSheet( + "background-color : rgb({r},{g},{b})".format(r=color[0] * 255, g=color[1] * 255, b=color[2] * 255)) + + def get_visualization_settings(self): + logarithmic = self.ui_dialog.checkBox_logarithmic_color.isChecked() + if self.ui_dialog.radioButton_absolute.isChecked(): + contrast_method = 'absolute' + contrast_min = self.ui_dialog.spinBox_absolute_min.value() + contrast_max = self.ui_dialog.spinBox_absolute_max.value() + elif self.ui_dialog.radioButton_percentile.isChecked(): + contrast_method = 'percentile' + contrast_min = self.ui_dialog.spinBox_percentile_min.value() + contrast_max = self.ui_dialog.spinBox_percentile_max.value() + else: + contrast_method = None + contrast_min = None + contrast_max = None + visualization_settings = {"density_settings": { + "nan_color": self.nan_color, + "color_map": self.ui_dialog.comboBox_color_map.currentText(), + 'logarithmic': logarithmic, + 'contrast_method': contrast_method, + 'contrast_min': contrast_min, + 'contrast_max': contrast_max, + }} + return visualization_settings + + def update_render_window(self): + self.write_ui_to_config() + visualization_settings = self.get_visualization_settings() + image_number = self.ui_dialog.spinBox_image_number.value() + self.render_window.render(image_number=image_number, visualization_settings=visualization_settings) + + def write_ui_to_config(self): + color_map = self.ui_dialog.comboBox_color_map.currentText() + nan_color = self.nan_color + self.model_view.set_config_by_key_sub_key("density_dialog", "color_map", color_map) + self.model_view.set_config_by_key_sub_key("density_dialog", "nan_color", nan_color) + + def handle_image_number_changed(self): + self.image_number = self.ui_dialog.spinBox_image_number.value() + self.ui_dialog.spinBox_image_number.setEnabled(False) + QtWidgets.QApplication.processEvents() + self.render_window.real_points = None + self.render_window.selector.select_all() + self.set_middle_point_index_min_max() + self.update_render_window() + self.ui_dialog.spinBox_image_number.setEnabled(True) + + def handle_image_color_map_changed(self): + self.update_render_window() + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/density_visualization/__init__.py b/view/density_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/density_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/density_visualization/matplotlib/__init__.py b/view/density_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/density_visualization/matplotlib/__init__.py @@ -0,0 +1 @@ + diff --git a/view/density_visualization/matplotlib/density_render_window.py b/view/density_visualization/matplotlib/density_render_window.py new file mode 100644 index 0000000..9843723 --- /dev/null +++ b/view/density_visualization/matplotlib/density_render_window.py @@ -0,0 +1,86 @@ +import copy + +import matplotlib.colors +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib import cm +from matplotlib.backends import backend_qt5agg +from matplotlib.pyplot import Figure + +from model_view import frontend_adapter + + +class DensityRenderWindow(QtCore.QObject): + lasso_selection_signal = QtCore.pyqtSignal(object) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + + def update(self): + self.figure.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, image_number, visualization_settings): + self.figure.clear() + self.real_points = None + self.figure.set_tight_layout(True) + density_settings = visualization_settings['density_settings'] + self.axes = self.figure.add_subplot(111) + self.render_density(image_number, density_settings) + self.update() + + def render_density(self, image_number, density_settings): + contrast_method = density_settings['contrast_method'] + contrast_min = density_settings['contrast_min'] + contrast_max = density_settings['contrast_max'] + density_array = self.model_view.get_density_array_by_image_number(image_number) + density_array = density_array.astype(float) + logarithmic = density_settings['logarithmic'] + # Choose normalizer + if logarithmic is True: + normalizer = matplotlib.colors.LogNorm + else: + normalizer = matplotlib.colors.Normalize + + # Make sure that min is smaller then max + try: + if contrast_max < contrast_min: + contrast_max = contrast_min + except TypeError: + pass + + # Choose contrast method + if contrast_method == 'percentile': + v_min, v_max = np.percentile(density_array[~np.isnan(density_array)], (contrast_min, contrast_max)) + if logarithmic and v_min < 1: + v_min = 1 + if v_max < v_min: + v_max = v_min + normalizer_instance = normalizer(vmin=v_min, vmax=v_max) + elif contrast_method == 'absolute': + normalizer_instance = normalizer(vmin=contrast_min, vmax=contrast_max) + else: + normalizer_instance = normalizer() + + color_map = copy.copy(cm.get_cmap(density_settings["color_map"])) + color_map.set_bad(color=density_settings["nan_color"]) + + self.axes.imshow(density_array, zorder=0, cmap=color_map, norm=normalizer_instance) + + def reset_render_window(self): + self.update() diff --git a/view/dialog_neural_networks.py b/view/dialog_neural_networks.py new file mode 100644 index 0000000..ba08348 --- /dev/null +++ b/view/dialog_neural_networks.py @@ -0,0 +1,227 @@ +import logging +import os +from PyQt5 import QtWidgets +import model_view.neural_networks +from model_view import frontend_adapter +from view.ui import ui_dialog_neural_networks + +log = logging.getLogger(__name__) + + +class DialogNeuralNetworks(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, file_path=None): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_neural_networks.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.load_config() + self.ui_dialog.pushButton_start_training.clicked.connect(self.handle_train_button) + self.ui_dialog.pushButton_detect.clicked.connect(self.handle_detect_button) + self.ui_dialog.pushButton_yolo_path.clicked.connect(self.handle_yolo_browse_file_system) + self.ui_dialog.pushButton_weights_path.clicked.connect(self.handle_weights_browse_file_system) + self.ui_dialog.pushButton_training_data_path.clicked.connect(self.handle_training_data_browse_file_system) + self.ui_dialog.pushButton_show_training_command.clicked.connect(self.handle_show_training_command) + self.ui_dialog.pushButton_show_detection_command.clicked.connect(self.handle_show_detection_command) + + def open_file_system_dialog(self, directory): + if directory == "": + directory = os.getcwd() + folderpath = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Folder', directory=directory) + return folderpath + + def validate_training_input(self): + incorrect_inputs = [] + if self.ui_dialog.lineEdit_yolo_path.text() == "": + incorrect_inputs.append("Yolo Path is empty, should point to yolov5 repository") + intepreter_message = self.check_for_python_interpreter() + if intepreter_message is not None: + incorrect_inputs.append(intepreter_message) + if self.ui_dialog.lineEdit_save_training_data.text() == "": + incorrect_inputs.append( + "Data Path is empty, should point to a path where the training data is or should be generated") + if len(incorrect_inputs) > 0: + error_string = "Following inputs are incorrect:\n" + for incorrect_input in incorrect_inputs: + error_string += incorrect_input + "\n" + message_box = QtWidgets.QMessageBox() + message_box.setText(error_string) + message_box.exec_() + return False + return True + + def validate_detect_input(self): + incorrect_inputs = [] + if self.ui_dialog.lineEdit_yolo_path.text() == "": + incorrect_inputs.append("Yolo Path is empty, should point to yolov5 repository") + + intepreter_message = self.check_for_python_interpreter() + if intepreter_message is not None: + incorrect_inputs.append(intepreter_message) + + if self.ui_dialog.lineEdit_save_training_data.text() == "": + incorrect_inputs.append( + "Data Path is empty, should point to a path where the training data is or should be generated") + if self.ui_dialog.lineEdit_weight_path.text() == "": + incorrect_inputs.append( + "Weight path is empty, it should point to the weights of a custom trained yolo network") + if len(incorrect_inputs) > 0: + error_string = "Following inputs are incorrect:\n" + for incorrect_input in incorrect_inputs: + error_string += incorrect_input + "\n" + message_box = QtWidgets.QMessageBox() + message_box.setText(error_string) + message_box.exec_() + return False + return True + + def check_for_python_interpreter(self): + interpreter_path = self.ui_dialog.lineEdit_virtual_environment_path.text() + if interpreter_path == "": + interpreter_path = os.path.join(self.ui_dialog.lineEdit_yolo_path.text(), "venv/bin/python") + if os.path.exists(interpreter_path): + self.ui_dialog.lineEdit_virtual_environment_path.setText(interpreter_path) + return None + else: + return "No Interpreter found at {}".format(interpreter_path) + + def handle_yolo_browse_file_system(self): + directory = self.ui_dialog.lineEdit_yolo_path.text() + folder_path = self.open_file_system_dialog(directory) + self.ui_dialog.lineEdit_yolo_path.setText(folder_path) + + def handle_weights_browse_file_system(self): + directory = self.ui_dialog.lineEdit_weight_path.text() + if directory == "": + directory = os.getcwd() + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Select File', directory=directory, options=options) + self.ui_dialog.lineEdit_weight_path.setText(file_path) + + def handle_training_data_browse_file_system(self): + directory = self.ui_dialog.lineEdit_save_training_data.text() + folder_path = self.open_file_system_dialog(directory) + self.ui_dialog.lineEdit_save_training_data.setText(folder_path) + + def load_config(self): + yolo_path = self.model_view.get_config_by_key_sub_key("yolo_settings", "yolo_path") + weight_path = self.model_view.get_config_by_key_sub_key("yolo_settings", "weight_path") + save_path = self.model_view.get_config_by_key_sub_key("yolo_settings", "save_path") + number_of_epochs = self.model_view.get_config_by_key_sub_key("yolo_settings", "number_of_epochs") + batch_size = self.model_view.get_config_by_key_sub_key("yolo_settings", "batch_size") + name = self.model_view.get_config_by_key_sub_key("yolo_settings", "name") + additional_parameters = self.model_view.get_config_by_key_sub_key("yolo_settings", "additional_parameters") + + self.ui_dialog.lineEdit_yolo_path.setText(yolo_path) + self.ui_dialog.lineEdit_weight_path.setText(weight_path) + self.ui_dialog.lineEdit_save_training_data.setText(save_path) + self.ui_dialog.spinBox_epochs.setValue(number_of_epochs) + self.ui_dialog.spinBox_batch_size.setValue(batch_size) + self.ui_dialog.lineEdit_name.setText(name) + self.ui_dialog.lineEdit_additional_parameters.setText(additional_parameters) + + def handle_train_button(self): + if not self.validate_training_input(): + return + self.ui_dialog.textBrowser.clear() + yolo_path = self.ui_dialog.lineEdit_yolo_path.text() + training_data_path = self.ui_dialog.lineEdit_save_training_data.text() + number_of_epochs = self.ui_dialog.spinBox_epochs.value() + weight_path = self.ui_dialog.lineEdit_weight_path.text() + interpreter_path = self.ui_dialog.lineEdit_virtual_environment_path.text() + batch_size = self.ui_dialog.spinBox_batch_size.value() + name = self.ui_dialog.lineEdit_name.text() + additional_parameters = self.ui_dialog.lineEdit_additional_parameters.text() + self.save_ui_inputs_to_config() + try: + model_view.neural_networks.start_training(self.model_view, yolo_path, interpreter_path, training_data_path, + number_of_epochs, + batch_size, weight_path=weight_path, name=name, + additional_parameters=additional_parameters, + call_back_print_function=self.output_to_text_browser) + except NotADirectoryError as e: + message_box = QtWidgets.QMessageBox(text="Path is not a directory") + message_box.exec_() + + def handle_detect_button(self): + if not self.validate_detect_input(): + return + self.ui_dialog.textBrowser.clear() + yolo_path = self.ui_dialog.lineEdit_yolo_path.text() + interpreter_path = self.ui_dialog.lineEdit_virtual_environment_path.text() + confidence_threshold = self.ui_dialog.doubleSpinBox_confidence_threshold.value() + training_data_path = self.ui_dialog.lineEdit_save_training_data.text() + weight_path = self.ui_dialog.lineEdit_weight_path.text() + name = self.ui_dialog.lineEdit_name.text() + additional_parameters = self.ui_dialog.lineEdit_additional_parameters.text() + self.save_ui_inputs_to_config() + try: + model_view.neural_networks.start_detecting(self.model_view, yolo_path, interpreter_path, training_data_path, + weight_path, + confidence_threshold=confidence_threshold, name=name, + additional_parameters=additional_parameters, + call_back_print_function=self.output_to_text_browser) + except NotADirectoryError as e: + message_box = QtWidgets.QMessageBox(text="Path is not a directory") + message_box.exec_() + + def output_to_text_browser(self, text): + self.ui_dialog.textBrowser.append(text) + + def save_ui_inputs_to_config(self): + yolo_path = self.ui_dialog.lineEdit_yolo_path.text() + weight_path = self.ui_dialog.lineEdit_weight_path.text() + save_path = self.ui_dialog.lineEdit_save_training_data.text() + number_of_epochs = self.ui_dialog.spinBox_epochs.value() + batch_size = self.ui_dialog.spinBox_batch_size.value() + name = self.ui_dialog.lineEdit_name.text() + additional_parameters = self.ui_dialog.lineEdit_additional_parameters.text() + + self.model_view.set_config_by_key_sub_key("yolo_settings", "yolo_path", yolo_path) + self.model_view.set_config_by_key_sub_key("yolo_settings", "weight_path", weight_path) + self.model_view.set_config_by_key_sub_key("yolo_settings", "save_path", save_path) + self.model_view.set_config_by_key_sub_key("yolo_settings", "number_of_epochs", number_of_epochs) + self.model_view.set_config_by_key_sub_key("yolo_settings", "batch_size", batch_size) + self.model_view.set_config_by_key_sub_key("yolo_settings", "name", name) + self.model_view.set_config_by_key_sub_key("yolo_settings", "additional_parameters", additional_parameters) + + def handle_show_detection_command(self): + if not self.validate_detect_input(): + return + self.ui_dialog.textBrowser.clear() + if not self.validate_detect_input(): + return + yolo_path = self.ui_dialog.lineEdit_yolo_path.text() + interpreter_path = self.ui_dialog.lineEdit_virtual_environment_path.text() + confidence_threshold = self.ui_dialog.doubleSpinBox_confidence_threshold.value() + training_data_path = self.ui_dialog.lineEdit_save_training_data.text() + weight_path = self.ui_dialog.lineEdit_weight_path.text() + name = self.ui_dialog.lineEdit_name.text() + additional_parameters = self.ui_dialog.lineEdit_additional_parameters.text() + self.save_ui_inputs_to_config() + command = model_view.neural_networks.get_detection_command(self.model_view, yolo_path, interpreter_path, + training_data_path, + weight_path, + confidence_threshold=confidence_threshold, name=name, + additional_parameters=additional_parameters) + self.ui_dialog.textBrowser.setText(command) + + def handle_show_training_command(self): + if not self.validate_training_input(): + return + yolo_path = self.ui_dialog.lineEdit_yolo_path.text() + interpreter_path = self.ui_dialog.lineEdit_virtual_environment_path.text() + training_data_path = self.ui_dialog.lineEdit_save_training_data.text() + number_of_epochs = self.ui_dialog.spinBox_epochs.value() + weight_path = self.ui_dialog.lineEdit_weight_path.text() + batch_size = self.ui_dialog.spinBox_batch_size.value() + name = self.ui_dialog.lineEdit_name.text() + additional_parameters = self.ui_dialog.lineEdit_additional_parameters.text() + self.save_ui_inputs_to_config() + command = model_view.neural_networks.get_training_command(self.model_view, yolo_path, interpreter_path, + training_data_path, + number_of_epochs, + batch_size, weight_path=weight_path, name=name, + additional_parameters=additional_parameters) + self.ui_dialog.textBrowser.clear() + self.ui_dialog.textBrowser.setText(command) diff --git a/view/export_dialog.py b/view/export_dialog.py new file mode 100644 index 0000000..0e1423c --- /dev/null +++ b/view/export_dialog.py @@ -0,0 +1,383 @@ +import os + +from PyQt5 import QtWidgets, QtCore, QtGui + +from model_view import frontend_adapter +from view.image_visualization.matplotlib.rendering_methods import visualize_as_image as image_matplotlib +from view.image_visualization.vtk.rendering_methods import visualize_as_image as image_vtk +from view.image_visualization.vtk.rendering_methods import visualize_as_video +from view.ui import ui_dialog_export + + +class ExportDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, export_method=None, current_image_number=None): + self.export_method = export_method + self.current_image_number = current_image_number + self.render_widget = None + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_export.Ui_DialogExport() + self.ui_dialog.setupUi(self) + self.ui_dialog.label_render_back_end.setHidden(True) + self.ui_dialog.comboBox_render_back_end.setHidden(True) + self.ui_dialog.pushButtonBrowseFilePath.clicked.connect(self.handle_file_dialog) + self.ui_dialog.pushButton_show_preview.clicked.connect(self.handle_show_preview_button) + self.ui_dialog.pushButton_set_start_frame_time_stamp_to_zero.clicked.connect( + self.set_start_time_stamp_to_current_image_time_stamp) + if export_method is None or export_method == "video": + self.set_up_video_export_dialog() + elif export_method == "image": + self.set_up_export_image_dialog() + elif export_method == "RTSSTEM": + self.set_up_export_rtsstem_dialog() + elif export_method == "gwy": + self.set_up_export_gwy_dialog() + + def set_up_video_export_dialog(self): + self.set_up_time_stamps_settings_dialog() + file_path = self.model_view.get_config_by_key_sub_key("export_video", 'file_path') + start_frame = self.model_view.get_config_by_key_sub_key("export_video", 'start_frame') + end_frame = self.model_view.get_config_by_key_sub_key("export_video", 'end_frame') + frames_per_second = self.model_view.get_config_by_key_sub_key("export_video", 'frames_per_second') + resolution = self.model_view.get_config_by_key_sub_key("export_video", 'resolution') + file_format = self.model_view.get_config_by_key_sub_key("export_video", 'file_format') + background_color = self.model_view.get_config_by_key_sub_key("export_video", 'background_color') + show_rag = self.model_view.get_config_by_key_sub_key("export_video", "show_rag") + show_features = self.model_view.get_config_by_key_sub_key("export_video", "show_features") + show_scale = self.model_view.get_config_by_key_sub_key('export_video', 'show_scale') + if not end_frame: + self.ui_dialog.spinBoxEndFrame.setValue(self.model_view.get_maximum_image_index_by_string()) + else: + self.ui_dialog.spinBoxEndFrame.setValue(end_frame) + self.ui_dialog.spinBoxEndFrame.setMaximum(self.model_view.get_maximum_image_index_by_string()) + self.ui_dialog.spinBoxStartFrame.setValue(start_frame or 0) + self.ui_dialog.lineEditFilePath.setText(file_path) + self.ui_dialog.lineEditFilePath.textChanged.connect(self.file_path_changed) + self.ui_dialog.spinBoxFramesPerSecond.setValue(frames_per_second) + self.ui_dialog.spinBoxResolutionX.setValue(resolution[0]) + self.ui_dialog.spinBoxResolutionY.setValue(resolution[1]) + self.ui_dialog.comboBoxFileFormat.setCurrentText(file_format) + self.ui_dialog.comboBoxFileFormat.currentIndexChanged.connect(self.file_format_changed) + self.ui_dialog.spinBoxColorR.setValue(background_color[0]) + self.ui_dialog.spinBoxColorG.setValue(background_color[1]) + self.ui_dialog.spinBoxColorB.setValue(background_color[2]) + self.ui_dialog.checkBox_show_rag.setChecked(show_rag) + self.ui_dialog.checkBox_show_features.setChecked(show_features) + self.ui_dialog.checkBox_show_scale.setChecked(show_scale) + self.update_show_message() + self.set_up_show_preview_connection() + + def file_format_changed(self): + self.update_show_message() + + def file_path_changed(self): + self.update_show_message() + + def update_show_message(self): + if self.ui_dialog.comboBoxFileFormat.currentText() == ".gif": + self.ui_dialog.label_message.setText( + "Export Video as gif will create a folder at `{file_path}` (Will fail if the folder already exists) with all jpeg of the video and a gif at `{file_path}.gif` ".format( + file_path=self.ui_dialog.lineEditFilePath.text())) + self.ui_dialog.label_message.setStyleSheet("background-color: red") + else: + self.ui_dialog.label_message.setText("") + self.ui_dialog.label_message.setStyleSheet("") + + def set_up_export_image_dialog(self): + self.set_up_time_stamps_settings_dialog() + self.ui_dialog.spinBoxStartFrame.setMaximum(self.model_view.get_maximum_image_index_by_string()) + self.ui_dialog.label_to.hide() + self.ui_dialog.spinBoxEndFrame.hide() + self.ui_dialog.label_fps.hide() + self.ui_dialog.spinBoxFramesPerSecond.hide() + self.ui_dialog.label_render_back_end.setHidden(False) + self.ui_dialog.comboBox_render_back_end.setHidden(False) + self.ui_dialog.comboBox_render_back_end.addItems(["vtk", "matplotlib"]) + file_path = self.model_view.get_config_by_key_sub_key("export_image", 'file_path') + render_backend = self.model_view.get_config_by_key_sub_key("export_image", "render_backend") + start_frame = self.current_image_number + resolution = self.model_view.get_config_by_key_sub_key("export_image", 'resolution') + background_color = self.model_view.get_config_by_key_sub_key("export_image", 'background_color') + show_rag = self.model_view.get_config_by_key_sub_key("export_image", "show_rag") + show_features = self.model_view.get_config_by_key_sub_key("export_image", "show_features") + show_scale = self.model_view.get_config_by_key_sub_key("export_image", "show_scale") + self.ui_dialog.spinBoxEndFrame.setMaximum(self.model_view.get_maximum_image_index_by_string()) + self.ui_dialog.spinBoxStartFrame.setValue(start_frame or 0) + self.ui_dialog.lineEditFilePath.setText(file_path) + self.ui_dialog.spinBoxResolutionX.setValue(resolution[0]) + self.ui_dialog.spinBoxResolutionY.setValue(resolution[1]) + self.ui_dialog.spinBoxColorR.setValue(background_color[0]) + self.ui_dialog.spinBoxColorG.setValue(background_color[1]) + self.ui_dialog.spinBoxColorB.setValue(background_color[2]) + self.ui_dialog.checkBox_show_rag.setChecked(show_rag) + self.ui_dialog.checkBox_show_features.setChecked(show_features) + self.ui_dialog.label_frame_number.setText("Frame Number") + self.ui_dialog.comboBox_render_back_end.setCurrentText(render_backend) + self.set_up_image_export_file_formats() + file_format = self.model_view.get_config_by_key_sub_key("export_image", 'file_format') + self.ui_dialog.comboBoxFileFormat.setCurrentText(file_format) + self.ui_dialog.comboBox_render_back_end.currentTextChanged.connect(self.set_up_image_export_file_formats) + self.ui_dialog.checkBox_show_scale.setChecked(show_scale) + self.set_up_show_preview_connection() + + def set_up_image_export_file_formats(self): + self.ui_dialog.comboBoxFileFormat.clear() + if self.ui_dialog.comboBox_render_back_end.currentText() == "vtk": + self.ui_dialog.comboBoxFileFormat.addItems(["png", "jpg", "pickle"]) + else: + self.ui_dialog.comboBoxFileFormat.addItems({"png", "svg", "pdf", "jpg", "eps", "ps", "pickle"}) + + def set_up_export_rtsstem_dialog(self): + self.ui_dialog.label_frame_number.setText("Frame Number") + self.ui_dialog.spinBoxStartFrame.setMaximum(self.model_view.get_maximum_image_index_by_string()) + self.ui_dialog.label_to.hide() + self.ui_dialog.spinBoxEndFrame.hide() + self.ui_dialog.label_fps.hide() + self.ui_dialog.spinBoxFramesPerSecond.hide() + self.ui_dialog.label_file_format.hide() + self.ui_dialog.comboBoxFileFormat.hide() + self.ui_dialog.label_resolution.hide() + self.ui_dialog.spinBoxResolutionX.hide() + self.ui_dialog.spinBoxResolutionY.hide() + self.ui_dialog.label_7.hide() + self.ui_dialog.label_background_color.hide() + self.ui_dialog.label_9.hide() + self.ui_dialog.label_10.hide() + self.ui_dialog.label_11.hide() + self.ui_dialog.spinBoxColorR.hide() + self.ui_dialog.spinBoxColorG.hide() + self.ui_dialog.spinBoxColorB.hide() + self.ui_dialog.pushButton_show_preview.hide() + + def set_up_export_gwy_dialog(self): + file_path = self.model_view.get_config_by_key_sub_key('export_gwy', 'file_path') + frame_number = self.model_view.get_config_by_key_sub_key('export_gwy', 'frame_number') + title_raw = self.model_view.get_config_by_key_sub_key('export_gwy', 'image_title_raw') + title_filtered = self.model_view.get_config_by_key_sub_key('export_gwy', 'image_title_filtered') + self.ui_dialog.lineEditFilePath.setText(file_path) + self.ui_dialog.label_frame_number.setText("Frame Number") + self.ui_dialog.spinBoxStartFrame.setMaximum(self.model_view.get_maximum_image_index_by_string()) + self.ui_dialog.spinBoxStartFrame.setValue(frame_number or 0) + self.ui_dialog.label_to.hide() + self.ui_dialog.gridLayout.addWidget(QtWidgets.QLabel("Raw Image Title"), 3, 0) + self.line_edit_raw_title = QtWidgets.QLineEdit("raw_image_title") + self.line_edit_raw_title.setText(title_raw) + self.ui_dialog.gridLayout.addWidget(self.line_edit_raw_title, 3, 1) + self.ui_dialog.gridLayout.addWidget(QtWidgets.QLabel("Filtered Image Title"), 4, 0) + self.line_edit_filtered_title = QtWidgets.QLineEdit("filtered_image_title") + self.line_edit_filtered_title.setText(title_filtered) + self.ui_dialog.groupBox_time_stamp_settings.hide() + self.ui_dialog.gridLayout.addWidget(self.line_edit_filtered_title, 4, 1) + self.ui_dialog.spinBoxEndFrame.hide() + self.ui_dialog.label_fps.hide() + self.ui_dialog.spinBoxFramesPerSecond.hide() + self.ui_dialog.label_file_format.hide() + self.ui_dialog.comboBoxFileFormat.hide() + self.ui_dialog.label_resolution.hide() + self.ui_dialog.spinBoxResolutionX.hide() + self.ui_dialog.spinBoxResolutionY.hide() + self.ui_dialog.label_7.hide() + self.ui_dialog.label_background_color.hide() + self.ui_dialog.label_9.hide() + self.ui_dialog.label_10.hide() + self.ui_dialog.label_11.hide() + self.ui_dialog.spinBoxColorR.hide() + self.ui_dialog.spinBoxColorG.hide() + self.ui_dialog.spinBoxColorB.hide() + self.ui_dialog.pushButton_show_preview.hide() + + def handle_file_dialog(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path = self.ui_dialog.lineEditFilePath.text() + if file_path == "": + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', file_path, "All Files(*.*)", + options=options) + self.ui_dialog.lineEditFilePath.setText(file_path) + + def handle_show_preview_button(self): + self.preview_dialog = QtWidgets.QDialog() + flags = self.preview_dialog.windowFlags() + self.preview_dialog.setWindowFlags(flags | QtCore.Qt.Tool) + self.render_widget = QtWidgets.QWidget() + self.show_preview_layout = QtWidgets.QGridLayout() + self.preview_dialog.setLayout(self.show_preview_layout) + self.show_preview_layout.addWidget(self.render_widget) + self.preview_dialog.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + self.preview_dialog.show() + self.update_preview_image() + + def set_up_show_preview_connection(self): + self.ui_dialog.spinBoxStartFrame.valueChanged.connect(self.update_preview_image) + self.ui_dialog.checkBox_show_rag.clicked.connect(self.update_preview_image) + self.ui_dialog.checkBox_show_features.clicked.connect(self.update_preview_image) + self.ui_dialog.checkBox_show_scale.clicked.connect(self.update_preview_image) + self.ui_dialog.spinBoxResolutionX.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBoxResolutionY.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBoxColorR.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBoxColorG.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBoxColorB.editingFinished.connect(self.update_preview_image) + + def show_preview_close_event(self, *args, **kwargs): + print("Close") + self.render_widget = None + image_vtk.delete_render_window() + + def update_preview_image(self): + if self.render_widget is None: + return + self.set_time_stamp_settings() + start_frame = self.ui_dialog.spinBoxStartFrame.value() + resolution = [self.ui_dialog.spinBoxResolutionX.value(), self.ui_dialog.spinBoxResolutionY.value()] + background_color = [self.ui_dialog.spinBoxColorR.value(), self.ui_dialog.spinBoxColorG.value(), + self.ui_dialog.spinBoxColorB.value()] + show_rag = self.ui_dialog.checkBox_show_rag.isChecked() + show_features = self.ui_dialog.checkBox_show_features.isChecked() + show_scale = self.ui_dialog.checkBox_show_scale.isChecked() + self.render_widget.setFixedSize(resolution[0], resolution[1]) + self.preview_dialog.resize(self.preview_dialog.sizeHint()) + image_vtk.visualize_preview_image(self.model_view, background_color, start_frame, + resolution, widget=self.render_widget, + show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + + def set_up_time_stamps_settings_dialog(self): + self.ui_dialog.checkBox_timestamps_show.setChecked(self.get_time_stamp_config_by_key('show_time_stamps')) + self.ui_dialog.checkBox_show_scan_direction.setChecked(self.get_time_stamp_config_by_key('show_scan_direction')) + self.ui_dialog.comboBox_timestamps_unit.setCurrentText(self.get_time_stamp_config_by_key('unit')) + self.ui_dialog.spinBox_timestamps_red.setValue(self.get_time_stamp_config_by_key('color')['red']) + self.ui_dialog.spinBox_timestamps_green.setValue(self.get_time_stamp_config_by_key('color')['green']) + self.ui_dialog.spinBox_timestamps_blue.setValue(self.get_time_stamp_config_by_key('color')['blue']) + self.ui_dialog.spinBox_timestamps_pre_decimals.setValue( + self.get_time_stamp_config_by_key('digits_before')) + self.ui_dialog.spinBox_timestamps_decimals.setValue(self.get_time_stamp_config_by_key('digits_after')) + self.ui_dialog.spinBox_timestamps_text_font_size.setValue(self.get_time_stamp_config_by_key('font_size')) + self.ui_dialog.spinBox_timestamps_position_x.setValue(self.get_time_stamp_config_by_key('position')[0]) + self.ui_dialog.spinBox_timestamps_position_y.setValue(self.get_time_stamp_config_by_key('position')[1]) + self.ui_dialog.doubleSpinBox_start_time.setValue(self.get_time_stamp_config_by_key('start_time')) + self.ui_dialog.lineEdit_pre_text.setText(self.get_time_stamp_config_by_key("pre_text")) + self.connect_time_stamp_widgets() + + def connect_time_stamp_widgets(self): + self.ui_dialog.checkBox_timestamps_show.clicked.connect(self.update_preview_image) + self.ui_dialog.checkBox_show_scan_direction.clicked.connect(self.update_preview_image) + self.ui_dialog.comboBox_timestamps_unit.currentIndexChanged.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_red.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_green.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_blue.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_pre_decimals.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_decimals.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_text_font_size.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_position_x.editingFinished.connect(self.update_preview_image) + self.ui_dialog.spinBox_timestamps_position_y.editingFinished.connect(self.update_preview_image) + self.ui_dialog.doubleSpinBox_start_time.editingFinished.connect(self.update_preview_image) + self.ui_dialog.lineEdit_pre_text.editingFinished.connect(self.update_preview_image) + + def set_time_stamp_settings(self): + color_dict = {'red': self.ui_dialog.spinBox_timestamps_red.value(), + 'green': self.ui_dialog.spinBox_timestamps_green.value(), + 'blue': self.ui_dialog.spinBox_timestamps_blue.value()} + self.set_time_stamp_config_by_key('color', color_dict) + self.set_time_stamp_config_by_key('font_size', self.ui_dialog.spinBox_timestamps_text_font_size.value()) + self.set_time_stamp_config_by_key('digits_after', self.ui_dialog.spinBox_timestamps_decimals.value()) + self.set_time_stamp_config_by_key('digits_before', self.ui_dialog.spinBox_timestamps_pre_decimals.value()) + self.set_time_stamp_config_by_key('show_time_stamps', self.ui_dialog.checkBox_timestamps_show.isChecked()) + self.set_time_stamp_config_by_key('show_scan_direction', + self.ui_dialog.checkBox_show_scan_direction.isChecked()) + self.set_time_stamp_config_by_key('unit', self.ui_dialog.comboBox_timestamps_unit.currentText()) + self.set_time_stamp_config_by_key('position', + [self.ui_dialog.spinBox_timestamps_position_x.value(), + self.ui_dialog.spinBox_timestamps_position_y.value()]) + self.set_time_stamp_config_by_key('start_time', self.ui_dialog.doubleSpinBox_start_time.value()) + self.set_time_stamp_config_by_key('pre_text', self.ui_dialog.lineEdit_pre_text.text()) + + def get_time_stamp_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('time_stamps', key) + + def set_time_stamp_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('time_stamps', key, value) + + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: + enter_key_list = [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return] + if event.key() not in enter_key_list: + try: + QtWidgets.QDialog.keyPressEvent(event) + except: + pass + + def accept(self) -> None: + file_path = self.ui_dialog.lineEditFilePath.text() + start_frame = self.ui_dialog.spinBoxStartFrame.value() + end_frame = self.ui_dialog.spinBoxEndFrame.value() + frames_per_second = self.ui_dialog.spinBoxFramesPerSecond.value() + resolution = [self.ui_dialog.spinBoxResolutionX.value(), self.ui_dialog.spinBoxResolutionY.value()] + file_format = self.ui_dialog.comboBoxFileFormat.currentText() + background_color = [self.ui_dialog.spinBoxColorR.value(), self.ui_dialog.spinBoxColorG.value(), + self.ui_dialog.spinBoxColorB.value()] + show_scale = self.ui_dialog.checkBox_show_scale.isChecked() + show_rag = self.ui_dialog.checkBox_show_rag.isChecked() + show_features = self.ui_dialog.checkBox_show_features.isChecked() + if self.export_method == "video": + self.model_view.set_config_by_key_sub_key("export_video", 'file_path', file_path) + self.model_view.set_config_by_key_sub_key("export_video", 'start_frame', start_frame) + self.model_view.set_config_by_key_sub_key("export_video", 'end_frame', end_frame) + self.model_view.set_config_by_key_sub_key("export_video", 'frames_per_second', frames_per_second) + self.model_view.set_config_by_key_sub_key("export_video", 'resolution', resolution) + self.model_view.set_config_by_key_sub_key("export_video", 'file_format', file_format) + self.model_view.set_config_by_key_sub_key("export_video", 'background_color', background_color) + self.model_view.set_config_by_key_sub_key("export_video", 'show_rag', show_rag) + self.model_view.set_config_by_key_sub_key("export_video", 'show_features', show_features) + self.model_view.set_config_by_key_sub_key("export_video", 'show_scale', show_scale) + self.set_time_stamp_settings() + visualize_as_video.export_as_video(file_path=file_path, model_view=self.model_view, + background_color=background_color, + start_frame=start_frame, end_frame=end_frame, + frames_per_second=frames_per_second, resolution=resolution, + file_format=file_format, show_rag=show_rag, show_features=show_features, + show_scale=show_scale) + self.model_view.generate_log_file(file_path + file_format + ".log", additional_information='video') + + elif self.export_method == "image": + render_backend = self.ui_dialog.comboBox_render_back_end.currentText() + self.model_view.set_config_by_key_sub_key("export_image", 'file_path', file_path) + self.model_view.set_config_by_key_sub_key("export_image", 'frame', start_frame) + self.model_view.set_config_by_key_sub_key("export_image", 'resolution', resolution) + self.model_view.set_config_by_key_sub_key("export_image", 'file_format', file_format) + self.model_view.set_config_by_key_sub_key("export_image", 'background_color', background_color) + self.model_view.set_config_by_key_sub_key("export_image", 'show_rag', show_rag) + self.model_view.set_config_by_key_sub_key("export_image", 'show_features', show_features) + self.model_view.set_config_by_key_sub_key("export_image", 'render_backend', render_backend) + self.model_view.set_config_by_key_sub_key("export_image", 'show_scale', show_scale) + self.set_time_stamp_settings() + if render_backend == "vtk": + render_method = image_vtk + else: + render_method = image_matplotlib + render_method.export_as_image(model_view=self.model_view, file_path=file_path, + background_color=background_color, + image_number=start_frame, resolution=resolution, file_format=file_format, + show_rag=show_rag, show_features=show_features, show_scale=show_scale) + self.model_view.generate_log_file(file_path + file_format + ".log", additional_information='image') + + + elif self.export_method == "RTSSTEM": + self.model_view.export_image_as_rtsstem_format(file_path=file_path, image_number=start_frame) + + elif self.export_method == "gwy": + raw_image_title = self.line_edit_raw_title.text() + filtered_image_title = self.line_edit_filtered_title.text() + self.model_view.set_config_by_key_sub_key('export_gwy', 'file_path', file_path) + self.model_view.set_config_by_key_sub_key('export_gwy', 'frame', start_frame) + self.model_view.set_config_by_key_sub_key('export_gwy', 'image_title_raw', raw_image_title) + self.model_view.set_config_by_key_sub_key('export_gwy', 'image_title_filtered', filtered_image_title) + self.model_view.export_as_gwy(file_path=file_path, frame_number=start_frame, + raw_image_title=raw_image_title, + filtered_image_title=filtered_image_title) + + self.done(QtWidgets.QDialog.Accepted) + + def set_start_time_stamp_to_current_image_time_stamp(self): + image_time_stamp = self.model_view.get_time_stamp_by_frame(self.ui_dialog.spinBoxStartFrame.value(), + self.ui_dialog.comboBox_timestamps_unit.currentText()) + self.ui_dialog.doubleSpinBox_start_time.setValue(image_time_stamp) diff --git a/view/export_xyz_dialog.py b/view/export_xyz_dialog.py new file mode 100644 index 0000000..abbf413 --- /dev/null +++ b/view/export_xyz_dialog.py @@ -0,0 +1,54 @@ +import os + +from PyQt5 import QtWidgets + +from view.ui import ui_dialog_export_xyz + + +class ExportXYZDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent=parent) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_export_xyz.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.model_view = model_view + self.set_up_ui_fields() + + def set_up_ui_fields(self): + file_path = self.model_view.get_config_by_key_sub_key("export_xyz", 'file_path') + comment = self.model_view.get_config_by_key_sub_key("export_xyz", 'comment') + x_stretch_factor = self.model_view.get_config_by_key_sub_key("export_xyz", 'x_stretch_factor') + y_stretch_factor = self.model_view.get_config_by_key_sub_key("export_xyz", 'y_stretch_factor') + self.ui_dialog.lineEdit_file_path.setText(file_path) + self.ui_dialog.lineEdit_comment.setText(comment) + self.ui_dialog.doubleSpinBox_x_direction_stretch.setValue(x_stretch_factor) + self.ui_dialog.doubleSpinBox_y_direction_stretch.setValue(y_stretch_factor) + self.ui_dialog.pushButton_browse_file_system.clicked.connect(self.handle_file_dialog) + self.ui_dialog.pushButton_auto_scale_to_angstrom.clicked.connect(self.handle_scale_to_angstrom) + + def handle_scale_to_angstrom(self): + x_scale = 1e10 + y_scale = 1e10 + self.ui_dialog.doubleSpinBox_x_direction_stretch.setValue(x_scale) + self.ui_dialog.doubleSpinBox_y_direction_stretch.setValue(y_scale) + + def handle_file_dialog(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path = self.ui_dialog.lineEdit_file_path.text() + if file_path == "": + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', file_path, "All Files(*.*)", + options=options) + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def accept(self) -> None: + file_path = self.ui_dialog.lineEdit_file_path.text() + comment = self.ui_dialog.lineEdit_comment.text() + x_stretch_factor = self.ui_dialog.doubleSpinBox_x_direction_stretch.value() + y_stretch_factore = self.ui_dialog.doubleSpinBox_y_direction_stretch.value() + self.model_view.set_config_by_key_sub_key("export_xyz", 'file_path', file_path) + self.model_view.set_config_by_key_sub_key("export_xyz", "comment", comment) + self.model_view.set_config_by_key_sub_key("export_xyz", 'x_stretch_factor', x_stretch_factor) + self.model_view.set_config_by_key_sub_key("export_xyz", 'y_stretch_factor', y_stretch_factore) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/export_zip_dialog.py b/view/export_zip_dialog.py new file mode 100644 index 0000000..1018c07 --- /dev/null +++ b/view/export_zip_dialog.py @@ -0,0 +1,102 @@ +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_export_zip + + +class ExportZipDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_export_zip.Ui_DialogExportZip() + self.ui_dialog.setupUi(self) + self.ui_dialog.pushButtonBrowseFilePath.clicked.connect(self.handle_file_dialog) + self.set_up_dialog() + + def set_up_dialog(self): + export_path = self.get_value_from_config('export_path') + export_video = self.get_value_from_config('export_video') + export_vsy = self.get_value_from_config('export_vsy') + export_original = self.get_value_from_config('export_original') + export_config = self.get_value_from_config('export_config') + export_filter_log = self.get_value_from_config('export_filter_log') + export_feature_list = self.get_value_from_config('export_feature_list') + export_video_frames_per_second = self.get_value_from_config('export_video_frames_per_second') + export_video_resolution = self.get_value_from_config('export_video_resolution') + export_video_file_format = self.get_value_from_config('export_video_file_format') + export_video_background_color = self.get_value_from_config('export_video_background_color') + export_images = self.get_value_from_config("export_images") + export_images_resolution = self.get_value_from_config('export_images_resolution') + export_images_file_format = self.get_value_from_config('export_images_file_format') + export_images_background_color = self.get_value_from_config('export_images_background_color') + self.ui_dialog.lineEditFilePath.setText(export_path) + self.ui_dialog.checkBox_export_config.setChecked(export_config) + self.ui_dialog.checkBox_export_feature_list.setChecked(export_feature_list) + self.ui_dialog.checkBox_export_filter_logs.setChecked(export_filter_log) + self.ui_dialog.checkBox_export_original.setChecked(export_original) + self.ui_dialog.checkBox_export_video.setChecked(export_video) + self.ui_dialog.checkBox_export_vsy.setChecked(export_vsy) + self.ui_dialog.spinBoxFramesPerSecond.setValue(export_video_frames_per_second) + self.ui_dialog.spinBoxResolutionX.setValue(export_video_resolution[0]) + self.ui_dialog.spinBoxResolutionY.setValue(export_video_resolution[1]) + self.ui_dialog.comboBoxFileFormat.setCurrentText(export_video_file_format) + self.ui_dialog.spinBoxColorR.setValue(export_video_background_color[0]) + self.ui_dialog.spinBoxColorG.setValue(export_video_background_color[1]) + self.ui_dialog.spinBoxColorB.setValue(export_video_background_color[2]) + self.ui_dialog.checkBox_export_images.setChecked(export_images) + self.ui_dialog.spinBox_image_resolution_x.setValue(export_images_resolution[0]) + self.ui_dialog.spinBox_image_resolution_y.setValue(export_images_resolution[1]) + self.ui_dialog.comboBox_images_file_format.setCurrentText(export_images_file_format) + self.ui_dialog.spinBox_images_background_color_r.setValue(export_images_background_color[0]) + self.ui_dialog.spinBox_images_background_color_g.setValue(export_images_background_color[1]) + self.ui_dialog.spinBox_images_background_color_b.setValue(export_images_background_color[2]) + + def get_value_from_config(self, key): + return self.model_view.get_config_by_key_sub_key('export_as_zip', key) + + def set_value_in_config(self, key, value): + self.model_view.set_config_by_key_sub_key('export_as_zip', key, value) + + def handle_file_dialog(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', "", "All Files(*.*)", options=options) + self.ui_dialog.lineEditFilePath.setText(file_path) + + def accept(self) -> None: + export_file_path = self.ui_dialog.lineEditFilePath.text() + export_video = self.ui_dialog.checkBox_export_video.isChecked() + export_vsy = self.ui_dialog.checkBox_export_vsy.isChecked() + export_original = self.ui_dialog.checkBox_export_original.isChecked() + export_config = self.ui_dialog.checkBox_export_config.isChecked() + export_filter_log = self.ui_dialog.checkBox_export_filter_logs.isChecked() + export_feature_list = self.ui_dialog.checkBox_export_feature_list.isChecked() + export_video_frames_per_second = self.ui_dialog.spinBoxFramesPerSecond.value() + export_video_resolution = [self.ui_dialog.spinBoxResolutionX.value(), self.ui_dialog.spinBoxResolutionY.value()] + export_video_file_format = self.ui_dialog.comboBoxFileFormat.currentText() + export_video_background_color = [self.ui_dialog.spinBoxColorR.value(), self.ui_dialog.spinBoxColorG.value(), + self.ui_dialog.spinBoxColorG.value()] + export_images = self.ui_dialog.checkBox_export_images.isChecked() + export_images_resolution = [self.ui_dialog.spinBox_image_resolution_x.value(), + self.ui_dialog.spinBox_image_resolution_y.value()] + export_images_background_color = [self.ui_dialog.spinBox_images_background_color_r.value(), + self.ui_dialog.spinBox_images_background_color_g.value(), + self.ui_dialog.spinBox_images_background_color_b.value()] + export_images_format = self.ui_dialog.comboBox_images_file_format.currentText() + self.set_value_in_config('export_path', export_file_path) + self.set_value_in_config('export_video', export_video) + self.set_value_in_config('export_vsy', export_vsy) + self.set_value_in_config('export_original', export_original) + self.set_value_in_config('export_config', export_config) + self.set_value_in_config('export_filter_log', export_filter_log) + self.set_value_in_config('export_feature_list', export_feature_list) + self.set_value_in_config('export_video_frames_per_second', export_video_frames_per_second) + self.set_value_in_config('export_video_resolution', export_video_resolution) + self.set_value_in_config('export_video_file_format', export_video_file_format) + self.set_value_in_config('export_video_background_color', export_video_background_color) + self.set_value_in_config('export_images', export_images) + self.set_value_in_config('export_images_resolution', export_images_resolution) + self.set_value_in_config('export_images_file_format', export_images_format) + self.set_value_in_config('export_images_background_color', export_images_background_color) + self.model_view.save_current_scan_in_zip_file(file_path=export_file_path) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/feature_analyse_visualization/__init__.py b/view/feature_analyse_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/feature_analyse_visualization/matplotlib/__init__.py b/view/feature_analyse_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/feature_analyse_visualization/matplotlib/feature_visualization_factory.py b/view/feature_analyse_visualization/matplotlib/feature_visualization_factory.py new file mode 100644 index 0000000..f9aa4d3 --- /dev/null +++ b/view/feature_analyse_visualization/matplotlib/feature_visualization_factory.py @@ -0,0 +1,14 @@ +from view.feature_analyse_visualization.matplotlib import visualize_rose_plot, \ + visualization_graph + + +class FeatureAnalyseVisualizationFactory(): + analye_visualization_dict = {"Jump Angle Rose Plot": visualize_rose_plot.JumpAngleVisualization, + "Jump Angle Distance Plot": visualization_graph.JumpAngleDistPlot, + "Feature Class Jump Distances Plot": visualization_graph.FeatureClassMovementDistancePlot,} + + @classmethod + def get_feature_visualization(cls, visualization_name, model_view, widget): + plot_class = cls.analye_visualization_dict[visualization_name] + plot = plot_class(widget=widget, model_view=model_view) + return plot diff --git a/view/feature_analyse_visualization/matplotlib/visualization_graph.py b/view/feature_analyse_visualization/matplotlib/visualization_graph.py new file mode 100644 index 0000000..0899160 --- /dev/null +++ b/view/feature_analyse_visualization/matplotlib/visualization_graph.py @@ -0,0 +1,60 @@ +import numpy as np +import seaborn + +from view.feature_analyse_visualization.matplotlib.visualize import PlotVisualization + + +class JumpAngleDistPlot(PlotVisualization): + def __init__(self, widget, model_view, *args, **kwargs): + PlotVisualization.__init__(self, widget=widget, model_view=model_view) + + def render(self, image_range, feature_class, feature_index, *args, **kwargs): + self.figure.clear() + teta_bins = 4 + self.ax = self.figure.add_subplot(1, 1, 1) + angles = self.model_view.get_jump_angle_list(image_range, feature_class, feature_index) + seaborn.distplot(angles, teta_bins, kde=True, hist=False, color='darkblue', kde_kws={'linewidth': 4}, + ax=self.ax) + self.ax.set_xlabel('Angle (θ°)') + self.ax.set_ylabel('Density') + self.canvas.draw() + + +class FeatureClassMovementDistancePlot(PlotVisualization): + def __init__(self, widget, model_view): + PlotVisualization.__init__(self, widget=widget, model_view=model_view) + + def render(self, image_range, feature_class, feature_index, threshold, selected_feature_classes, + selected_feature_dict, *args, + **kwargs): + self.figure.clear() + feature_class_movement_distance = [] + for feature_class_name in selected_feature_classes: + feature_list = selected_feature_dict[feature_class_name] + feature_class_movement_distance.extend( + self.model_view.get_feature_class_movement_distance(image_range, feature_class_name, feature_list)) + if len(feature_class_movement_distance) == 0: + self.canvas.draw() + return + self.ax = self.figure.add_subplot(111) + x_axis = np.arange(len(feature_class_movement_distance)) + self.ax.bar(x_axis, feature_class_movement_distance, snap=False) + if threshold is not None: + self.ax.plot([x_axis[0], x_axis[-1]], [threshold, threshold], color="g") + self.canvas.draw() + + +class JumpFrequencyPlot(PlotVisualization): + def __init__(self, widget, model_view): + PlotVisualization.__init__(self, widget=widget, model_view=model_view) + + def render(self, feature_class, feature_index, *args, **kwargs): + self.figure.clear() + teta_bins = 4 + self.ax = self.figure.add_subplot(1, 1, 1) + angles = self.model_view.get_jump_angle_list(feature_class, feature_index) + seaborn.distplot(angles, teta_bins, kde=True, hist=False, color='darkblue', kde_kws={'linewidth': 4}, + ax=self.ax) + self.ax.set_xlabel('Angle (θ°)') + self.ax.set_ylabel('Density') + self.canvas.draw() diff --git a/view/feature_analyse_visualization/matplotlib/visualize.py b/view/feature_analyse_visualization/matplotlib/visualize.py new file mode 100644 index 0000000..e4d4a91 --- /dev/null +++ b/view/feature_analyse_visualization/matplotlib/visualize.py @@ -0,0 +1,36 @@ +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + + +class PlotVisualization(QtCore.QObject): + left_click_position = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + self.set_up_matplotlib_interactor() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + + def clear(self): + self.figure.clear() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.xdata, event.ydata]) diff --git a/view/feature_analyse_visualization/matplotlib/visualize_rose_plot.py b/view/feature_analyse_visualization/matplotlib/visualize_rose_plot.py new file mode 100644 index 0000000..2cf36cb --- /dev/null +++ b/view/feature_analyse_visualization/matplotlib/visualize_rose_plot.py @@ -0,0 +1,29 @@ +import numpy as np + +from view.feature_analyse_visualization.matplotlib.visualize import PlotVisualization + + +class JumpAngleVisualization(PlotVisualization): + def __init__(self, widget, model_view): + PlotVisualization.__init__(self, widget=widget, model_view=model_view) + + def render(self, image_range, feature_class_index, feature_index, *args, **kwargs): + self.figure.clear() + self.ax = self.figure.add_subplot(1, 1, 1, projection='polar') + self.ax.set_theta_zero_location("S") + angles = self.model_view.get_jump_angle_list(image_range, feature_class_index, feature_index) + angles = (angles + np.pi) % (2 * np.pi) - np.pi + number_of_bins = 16 + bins = np.linspace(-np.pi, np.pi, num=number_of_bins + 1) + count, bin = np.histogram(angles, bins=bins) + widths = np.diff(bin) + area = count / angles.size + radius = (area / np.pi) ** .5 + + self.ax.bar(bin[:-1], radius, zorder=1, align='edge', width=widths, + edgecolor='C0', fill=False, linewidth=1) + self.ax.set_yticks([]) + label = ['$0$', r'$\pi/4$', r'$\pi/2$', r'$3\pi/4$', + r'$\pi$', r'$5\pi/4$', r'$3\pi/2$', r'$7\pi/4$'] + self.ax.set_xticklabels(label) + self.canvas.draw() diff --git a/view/feature_class_visualization_dialog.py b/view/feature_class_visualization_dialog.py new file mode 100644 index 0000000..7e811c1 --- /dev/null +++ b/view/feature_class_visualization_dialog.py @@ -0,0 +1,42 @@ + + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_feature_class_visualization + + +class FeatureClassVisualizationDialog(QtWidgets.QDialog): + def __init__(self, current_feature_class_name, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.current_feature_class_name = current_feature_class_name + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + + self.ui_dialog = ui_dialog_feature_class_visualization.Ui_DialogFeatureClassVisualization() + self.ui_dialog.setupUi(self) + self.set_up_visualization_settings() + self.ui_dialog.label_feature_class_name.setText("Feature Class: {}".format(current_feature_class_name)) + + def set_up_visualization_settings(self): + feature_geometry = self.model_view.get_feature_class_visualization_by_item_name("feature_geometry", + feature_class_name=self.current_feature_class_name) + self.ui_dialog.comboBox_feature_representation.setCurrentText(feature_geometry) + + feature_point_size = self.model_view.get_feature_class_visualization_by_item_name("feature_point_size", + feature_class_name=self.current_feature_class_name) + self.ui_dialog.doubleSpinBox_feature_point_size.setValue(feature_point_size) + + feature_thickness_percent = self.model_view.get_feature_class_visualization_by_item_name( + "feature_thickness_percent", + feature_class_name=self.current_feature_class_name) + self.ui_dialog.doubleSpinBox_feature_thickness.setValue(feature_thickness_percent) + + def accept(self) -> None: + visualization_representation = {} + visualization_representation['feature_geometry'] = self.ui_dialog.comboBox_feature_representation.currentText() + visualization_representation[ + 'feature_thickness_percent'] = self.ui_dialog.doubleSpinBox_feature_thickness.value() + visualization_representation['feature_point_size'] = self.ui_dialog.doubleSpinBox_feature_point_size.value() + self.model_view.set_feature_class_visualization_representation(self.current_feature_class_name, + visualization_representation) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/feature_grid_dialog.py b/view/feature_grid_dialog.py new file mode 100644 index 0000000..f16026b --- /dev/null +++ b/view/feature_grid_dialog.py @@ -0,0 +1,135 @@ + + +from PyQt5 import QtWidgets, QtCore, QtGui + +from model_view import frontend_adapter +from view.feature_grid_visualization.vtk import feature_grid_visualization +from view.ui import ui_dialog_feature_grid + + +class FeatureGridDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_feature_grid.Ui_FeatureGridDialog() + self.ui_dialog.setupUi(self) + self.set_up_ui_fields() + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.generate_render_window() + self.ui_dialog.comboBox_image_comparison_function.currentTextChanged.connect(self.handle_combo_box_changed) + self.ui_dialog.doubleSpinBox_point_size.valueChanged.connect(self.update_render_window) + self.ui_dialog.pushButton_color_selector.clicked.connect(self.handle_color_button) + self.ui_dialog.pushButton_detect_grid_points.clicked.connect(self.handle_detect_button) + self.ui_dialog.pushButton_clear_feature_grid_list.clicked.connect(self.handle_clear_all_button) + self.update_render_window() + + def generate_render_window(self): + self.render_window = feature_grid_visualization.FeatureGridVisualization( + widget=self.ui_dialog.widget_render_window, + model_view=self.model_view) + self.render_window.left_click_position.connect(self.handle_left_click) + self.render_window.right_click_position.connect(self.handle_right_click) + + def dump_feature_grid_settings(self): + feature_grid_visualization_settings = self.get_visualization_settings() + self.model_view.set_feature_grid_visualization_settings(feature_grid_visualization_settings) + + def set_up_ui_fields(self): + self._set_up_function_combo_box() + self.set_up_grid_settings() + self.set_up_detect_settings() + + def set_up_grid_settings(self): + feature_grid_visualization_settings = self.model_view.get_feature_grid_visualization_settings() + self.ui_dialog.doubleSpinBox_point_size.setValue(feature_grid_visualization_settings["point_size"]) + self.ui_dialog.comboBox_image_comparison_function.setCurrentText( + feature_grid_visualization_settings['function']) + self.color = feature_grid_visualization_settings["color"] + + def set_up_detect_settings(self): + feature_detection_parameters = self.model_view.get_config_by_key_sub_key("feature_grid_settings", + "detection_settings") + self.ui_dialog.doubleSpinBox_feature_auto_detect_min_sigma.setValue(feature_detection_parameters["min_sigma"]) + self.ui_dialog.doubleSpinBox_feature_auto_detect_max_sigma.setValue(feature_detection_parameters["max_sigma"]) + self.ui_dialog.spinBox_feature_auto_detect_num_sigma.setValue(feature_detection_parameters["num_sigma"]) + self.ui_dialog.doubleSpinBox_feature_auto_detect_threshold.setValue(feature_detection_parameters["threshold"]) + self.ui_dialog.doubleSpinBox_feature_auto_detect_overlap.setValue(feature_detection_parameters["overlap"]) + self.ui_dialog.checkBox_feature_auto_detect_detect_minima.setChecked( + feature_detection_parameters["detect_maxima"]) + self.ui_dialog.comboBox_feature_auto_detect_function.clear() + self.ui_dialog.comboBox_feature_auto_detect_function.addItems( + self.model_view.get_feature_detection_function_list()) + try: + self.ui_dialog.comboBox_feature_auto_detect_function.setCurrentText( + self.model_view.get_config_by_key_sub_key("feature_settings", 'feature_detector') + ) + except TypeError: + self.ui_dialog.comboBox_feature_auto_detect_function.setCurrentIndex(0) + + def get_visualization_settings(self): + point_size = self.ui_dialog.doubleSpinBox_point_size.value() + color = self.color + function = self.ui_dialog.comboBox_image_comparison_function.currentText() + feature_grid_visualization_settings = {"point_size": point_size, + "color": color, + "function": function, } + return feature_grid_visualization_settings + + def _set_up_function_combo_box(self): + image_functions = ["Average", "Minimum", "Maximum", ] + self.ui_dialog.comboBox_image_comparison_function.addItems(image_functions) + + def update_render_window(self): + visualization_settings = self.get_visualization_settings() + self.dump_feature_grid_settings() + self.render_window.render(visualization_settings) + + def handle_combo_box_changed(self): + self.update_render_window() + + def handle_left_click(self, position): + self.model_view.feature_grid_list_add_point(position) + self.update_render_window() + + def handle_right_click(self, position): + self.model_view.feature_grid_list_remove_point(position) + self.update_render_window() + + def handle_color_button(self): + color_dialog = QtWidgets.QColorDialog() + q_color = QtGui.QColor.fromRgb(*self.color) + color_from_dialog = color_dialog.getColor(initial=q_color, + options=QtWidgets.QColorDialog.DontUseNativeDialog | QtWidgets.QColorDialog.ShowAlphaChannel) + color = color_from_dialog.getRgb() + if color_from_dialog.isValid(): + self.color = color[:4] + self.update_render_window() + + def handle_clear_all_button(self): + self.model_view.clear_feature_grid_list() + self.update_render_window() + + def handle_detect_button(self): + self.model_view.clear_feature_grid_list() + feature_detection_parameters = {"min_sigma": self.ui_dialog.doubleSpinBox_feature_auto_detect_min_sigma.value(), + "max_sigma": self.ui_dialog.doubleSpinBox_feature_auto_detect_max_sigma.value(), + "num_sigma": self.ui_dialog.spinBox_feature_auto_detect_num_sigma.value(), + "threshold": self.ui_dialog.doubleSpinBox_feature_auto_detect_threshold.value(), + "overlap": self.ui_dialog.doubleSpinBox_feature_auto_detect_overlap.value(), + "detect_maxima": self.ui_dialog.checkBox_feature_auto_detect_detect_minima.isChecked(), } + image_function = self.ui_dialog.comboBox_image_comparison_function.currentText() + if image_function == "Minimum": + image = self.model_view.get_min_image() + elif image_function == "Maximum": + image = self.model_view.get_max_image() + else: + image = self.model_view.get_2d_average_image() + feature_list = self.model_view.auto_detect_features_in_image(image, parameters=feature_detection_parameters) + for feature in feature_list: + self.model_view.feature_grid_list_add_point(feature[:2]) + self.model_view.set_config_by_key_sub_key("feature_grid_settings", + "detection_settings", feature_detection_parameters) + self.update_render_window() + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/feature_grid_visualization/__init__.py b/view/feature_grid_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_grid_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_grid_visualization/vtk/__init__.py b/view/feature_grid_visualization/vtk/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_grid_visualization/vtk/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_grid_visualization/vtk/feature_grid_visualization.py b/view/feature_grid_visualization/vtk/feature_grid_visualization.py new file mode 100644 index 0000000..322b443 --- /dev/null +++ b/view/feature_grid_visualization/vtk/feature_grid_visualization.py @@ -0,0 +1,108 @@ + + +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.collections import EllipseCollection +from matplotlib.figure import Figure + +from model_view import frontend_adapter + + +class FeatureGridVisualization(QtCore.QObject): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + lasso_selection = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + QtCore.QObject.__init__(self) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + self.prev_image_function = None + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.set_up_matplotlib_interactor() + self.hexagon_point_list = [] + self.axes = self.figure.add_subplot(111) + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, visualization_settings): + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.render_image(visualization_settings) + self.render_feature_grid(visualization_settings=visualization_settings) + self.update() + + def render_image(self, visualization_settings): + image_function = visualization_settings["function"] + if image_function != self.prev_image_function: + self.image = self.get_image_by_image_function(image_function) + + step_size = self.model_view.get_step_size() + self.axes.imshow(self.image, cmap="gray", zorder=0, + extent=[0, len(self.image[0]) * step_size[0], len(self.image) * step_size[1], 0]) + self.prev_image_function = image_function + + def render_feature_grid(self, visualization_settings): + step_size = self.model_view.get_step_size() + color = visualization_settings["color"] + radius = visualization_settings["point_size"] * step_size[0] + feature_grid_list = self.model_view.get_feature_grid_list() + node_radius_list = [radius] * len(feature_grid_list) + color = [c / 255 for c in color] + if len(feature_grid_list) > 0: + circle_collection = EllipseCollection(node_radius_list, node_radius_list, + np.zeros_like(node_radius_list), + offsets=feature_grid_list, units='x', + color=color, + transOffset=self.axes.transData) + self.axes.add_collection(circle_collection) + + def get_image_by_image_function(self, image_function): + if image_function == "Minimum": + image = self.model_view.get_min_image() + elif image_function == "Maximum": + image = self.model_view.get_max_image() + elif image_function == "Average": + image = self.model_view.get_2d_average_image() + return image + + def generate_greyscale_image(self, image: np.array): + image_normalize = image.copy() + image_normalize -= np.nanmin(image_normalize) + image_normalize = np.nan_to_num(image_normalize) + + image_normalize = image_normalize / image_normalize.max() + image_normalize *= 255 + image_normalize = image_normalize.astype(int) + return image_normalize + + def reset_render_window(self): + self.update() + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.xdata, event.ydata]) + elif event.button == 3: + self.right_click_position.emit([event.xdata, event.ydata]) + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) diff --git a/view/feature_jump_visualization/__init__.py b/view/feature_jump_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_jump_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_jump_visualization/matplotlib/__init__.py b/view/feature_jump_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_jump_visualization/matplotlib/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_jump_visualization/matplotlib/jump_visualization.py b/view/feature_jump_visualization/matplotlib/jump_visualization.py new file mode 100644 index 0000000..5b8d792 --- /dev/null +++ b/view/feature_jump_visualization/matplotlib/jump_visualization.py @@ -0,0 +1,157 @@ + + +import copy + +import matplotlib.cm +import matplotlib.colorbar +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.collections import LineCollection, EllipseCollection +from matplotlib.figure import Figure +from mpl_toolkits.axes_grid1 import make_axes_locatable + +from model_view import frontend_adapter + + +class JumpVisualization(QtCore.QObject): + left_click_position = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.axes = self.figure.add_subplot(111) + self.canvas.mpl_connect("key_press_event", self.key_pressed) + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, selected_feature_classes, selected_feature_dict, threshold): + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.render_jumps(selected_feature_classes, selected_feature_dict) + self.render_background_image() + self.update() + + def render_background_image(self): + average_image = self.model_view.get_2d_average_image() + average_image[~np.isnan(average_image)] = 1 + average_image[np.isnan(average_image)] = 0 + image_zeros = average_image + step_size = self.model_view.get_step_size() + self.axes.imshow(image_zeros, cmap="gray", zorder=0, + extent=[0, len(image_zeros[0]) * step_size[0], len(image_zeros) * step_size[1], 0]) + + def render_jumps(self, selected_feature_classes, selected_feature_dict): + jump_position_list = [] + color_map_name = 'jet' + self.add_color_bar_to_render_window(color_map_name=color_map_name) + for feature_class_name in selected_feature_classes: + feature_list = selected_feature_dict[feature_class_name] + for feature_index in feature_list: + jump_positions = self.model_view.get_feature_jump_position_list(feature_class_name=feature_class_name, + feature_index=feature_index) + jump_position_list.append(jump_positions) + line_collection, jump_from_nan_collection, jump_to_nan_collection = self.get_feature_collections( + jump_positions, color_map_name=color_map_name) + self.axes.add_collection(line_collection) + if jump_from_nan_collection: + self.axes.add_collection(jump_from_nan_collection) + if jump_to_nan_collection: + self.axes.add_collection(jump_to_nan_collection) + + def add_color_bar_to_render_window(self, color_map_name): + color_map, color_normalizer = self.get_color_map_and_normalizer(color_map_name) + scalar_mappable = matplotlib.cm.ScalarMappable(norm=color_normalizer, cmap=color_map) + # create an axes on the right side of ax. The width of cax will be 5% + # of ax and the padding between cax and ax will be fixed at 0.05 inch. + divider = make_axes_locatable(self.axes) + cax = divider.append_axes("right", size="5%", pad=0.05) + self.figure.colorbar(scalar_mappable, cax=cax).set_label("Time in s") + + def get_color_map_and_normalizer(self, color_map_name): + time_stamps = self.model_view.get_time_stamps() + color_min = time_stamps[0] + color_max = time_stamps[-1] + color_map = copy.copy(matplotlib.pyplot.get_cmap(color_map_name)) + color_normalizer = matplotlib.colors.Normalize(vmin=color_min, vmax=color_max) + return color_map, color_normalizer + + def get_feature_collections(self, jump_positions, color_map_name): + jump_lines = [] + jump_lines_image_number = [] + jump_to_nan_position_list = [] + jump_from_nan_position_list = [] + for i in range(len(jump_positions)): + if i > 0: + jump_to_nan = any(np.isnan(jump_positions[i])) + jump_from_nan = any(np.isnan(jump_positions[i - 1])) + if jump_to_nan and jump_from_nan: + continue + elif jump_to_nan: + jump_to_nan_position_list.append(jump_positions[i - 1][:2]) + elif jump_from_nan: + jump_from_nan_position_list.append(jump_positions[i][:2]) + else: + jump_lines.append([jump_positions[i - 1][:2], jump_positions[i][:2]]) + jump_lines_image_number.append(i) + + # LINE Collection + cmap, normalizer = self.get_color_map_and_normalizer(color_map_name=color_map_name) + line_collection = LineCollection(jump_lines, transOffset=self.axes.transData, cmap=cmap, norm=normalizer) + time_stamps = np.asarray(self.model_view.get_time_stamps()) + jump_line_color_array = time_stamps[jump_lines_image_number] + line_collection.set_array(jump_line_color_array) + line_collection.set_linewidth(2) + line_collection.zorder = 1 + + # JUMP FROM NAN COLLECTION + jump_from_nan_position_collection = self.generate_circle_collection(jump_from_nan_position_list, + color=[0, 1, 0, 1]) + jump_to_nan_position_collection = self.generate_circle_collection(jump_to_nan_position_list, + color=[1, 0, 0, 1]) + return line_collection, jump_from_nan_position_collection, jump_to_nan_position_collection + + def generate_circle_collection(self, point_list, color, radius=1): + if len(point_list) > 0: + step_size = self.model_view.get_step_size() + node_radius_list_1 = [radius * step_size[0]] * len(point_list) + circle_collection = EllipseCollection(node_radius_list_1, node_radius_list_1, + angles=np.zeros_like(node_radius_list_1), + offsets=point_list, units='x', + transOffset=self.axes.transData, color=color) + return circle_collection + else: + return None + + def reset_render_window(self): + self.update() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.xdata, event.ydata]) + + def key_pressed(self, event): + pass diff --git a/view/feature_jump_visualization/vtk/__init__.py b/view/feature_jump_visualization/vtk/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_jump_visualization/vtk/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_rag_dialog.py b/view/feature_rag_dialog.py new file mode 100644 index 0000000..34ca210 --- /dev/null +++ b/view/feature_rag_dialog.py @@ -0,0 +1,265 @@ + + +import os +from collections import Counter + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.feature_rag_visualization.matplotlib import feature_rag_graph_visualization +from view.feature_rag_visualization.vtk import feature_rag_visualization +from view.ui import ui_dialog_feature_rag + + +class FeatureRagDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_feature_rag.Ui_Dialog() + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.ui_dialog.setupUi(self) + self.ui_dialog.spinBox_image_number.valueChanged.connect(self.handle_image_number_changed) + image_max_index = self.model_view.get_maximum_image_index() + self.initilize_selected_feature_dict() + self.generate_render_windows() + self.update_feature_class_list_widget() + self.ui_dialog.spinBox_image_number.setMinimum(0) + self.ui_dialog.spinBox_image_number.setMaximum(image_max_index) + self.ui_dialog.spinBox_image_number.setValue(image_number) + self.ui_dialog.horizontalSlider_image_number.setMinimum(0) + self.ui_dialog.horizontalSlider_image_number.setMaximum(image_max_index) + self.ui_dialog.pushButton_force_recreate_rags.clicked.connect(self.handle_force_recreate_rag) + self.ui_dialog.pushButton_feature_class_check_all.clicked.connect(self.handle_feature_class_check_all) + self.ui_dialog.pushButton_feature_class_uncheck_all.clicked.connect(self.handle_feature_class_uncheck_all) + self.ui_dialog.pushButton_feature_class_invert.clicked.connect(self.handle_feature_class_invert) + self.ui_dialog.pushButton_feature_list_check_all.clicked.connect(self.handle_feature_list_check_all) + self.ui_dialog.pushButton_feature_list_uncheck_all.clicked.connect(self.handle_feature_list_uncheck_all) + self.ui_dialog.pushButton_feature_list_invert.clicked.connect(self.handle_feature_list_invert) + self.ui_dialog.listWidget_feature_class.currentItemChanged.connect(self.handle_change_feature_class) + self.ui_dialog.listWidget_feature.currentItemChanged.connect(self.handle_change_feature_index) + self.ui_dialog.pushButton_export.clicked.connect(self.handle_export_dialog) + + def handle_image_number_changed(self): + self.update_render_windows() + + def generate_render_windows(self): + widget = self.ui_dialog.widget_render_window + image_number = self.ui_dialog.spinBox_image_number.value() + self.render_window = feature_rag_visualization.FeatureRagWindow(widget, model_view=self.model_view) + selected_feature_dict = self.get_back_end_selected_feature_dict() + self.render_window.render(image_number, selected_feature_classes=self.selected_feature_classes, + selected_feature_dict=selected_feature_dict) + self.render_window.reset_render_camera() + widget_graph = self.ui_dialog.widget_neighbor_graph + self.graph_render_window = feature_rag_graph_visualization.RagGraphVisualization(widget_graph, + model_view=self.model_view) + self.graph_render_window.left_click_position.connect(self.handle_left_click_graph_window) + + def handle_left_click_graph_window(self, position): + image_number = int(position[0]) + 1 + self.ui_dialog.spinBox_image_number.setValue(image_number) + + def handle_force_recreate_rag(self): + self.model_view.generate_feature_rags(force_recreate=True) + self.update_render_windows() + + def update_render_windows(self): + image_number = self.ui_dialog.spinBox_image_number.value() + selected_feature_dict = self.get_back_end_selected_feature_dict() + self.render_window.render(image_number, self.selected_feature_classes, selected_feature_dict) + self.graph_render_window.render(image_number, self.selected_feature_classes, selected_feature_dict) + + def get_feature_list_by_class_index(self, class_index): + try: + return self.selected_feature_dict_front_end[class_index] + except KeyError: + self.selected_feature_dict_front_end[class_index] = [] + return self.selected_feature_dict_front_end[class_index] + + def update_ui(self): + self.update_render_windows() + self.update_analyse_window() + + def handle_change_feature_class(self): + self.update_feature_list_widget() + + def handle_change_feature_index(self): + self.update_ui() + + def initilize_selected_feature_dict(self, selected_feature_classes=None): + feature_class_list = self.model_view.get_feature_classes_list() + if selected_feature_classes is not None: + self.selected_feature_classes = selected_feature_classes + else: + self.selected_feature_classes = [] + self.selected_feature_dict_front_end = {} + for feature_class_index, feature_class_name in enumerate(feature_class_list): + self.selected_feature_dict_front_end[feature_class_name] = [] + + def update_feature_class_list_widget(self): + feature_class_list = self.model_view.get_feature_classes_list() + self.ui_dialog.listWidget_feature_class.clear() + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature_class, + item_list=feature_class_list, + selected_item_list=self.selected_feature_classes, + check_box_call_back_function=self.get_selected_feature_classes) + self.ui_dialog.listWidget_feature_class.setCurrentRow(0) + self.update_feature_list_widget() + + def update_feature_list_widget(self): + current_feature_class_index = self.ui_dialog.listWidget_feature_class.currentRow() + self.ui_dialog.listWidget_feature.clear() + if current_feature_class_index >= 0: + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=current_feature_class_index) + current_feature_class_name = self.get_current_feature_class_name() + selected_item_list = self.get_feature_list_by_class_index(current_feature_class_name) + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature, + item_list=feature_list, selected_item_list=selected_item_list, + check_box_call_back_function=self.set_selected_features_in_feature_dict) + self.ui_dialog.listWidget_feature.setCurrentRow(0) + + def get_current_feature_class_name(self): + list_widget = self.ui_dialog.listWidget_feature_class + current_index = list_widget.currentRow() + list_widget_item = list_widget.item(current_index) + label = list_widget.itemWidget(list_widget_item).children()[1] + return label.text() + + def get_back_end_selected_feature_dict(self): + selected_feature_dict = {} + for class_name, feature_list in self.selected_feature_dict_front_end.items(): + selected_feature_dict[class_name] = [int(feature) for feature in feature_list] + return selected_feature_dict + + def generate_feature_list_with_checkboxes(self, list_widget, item_list, selected_item_list, + check_box_call_back_function=None): + for item in item_list: + # Create widget + # Add widget to QListWidget funList + layout = QtWidgets.QHBoxLayout() + label = QtWidgets.QLabel(item) + check_box = QtWidgets.QCheckBox() + if check_box_call_back_function is not None: + check_box.clicked.connect(check_box_call_back_function) + if item in selected_item_list: + check_box.setChecked(True) + layout.addWidget(label) + layout.addWidget(check_box) + layout.addStretch() + list_widget_item = QtWidgets.QListWidgetItem() + widget = QtWidgets.QWidget() + layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + widget.setLayout(layout) + list_widget_item.setSizeHint(widget.sizeHint()) + list_widget.addItem(list_widget_item) + list_widget.setItemWidget(list_widget_item, widget) + + def get_selected_items_by_list_widget(self, list_widget): + item_list = [] + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + label = list_widget.itemWidget(list_widget_item).children()[1] + if check_box.isChecked(): + item_list.append(label.text()) + return item_list + + def get_selected_feature_classes(self): + self.selected_feature_classes = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature_class) + self.update_ui() + + def set_selected_features_in_feature_dict(self): + feature_class_name = self.get_current_feature_class_name() + selected_features = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature) + self.selected_feature_dict_front_end[feature_class_name] = selected_features + self.update_ui() + + def handle_feature_class_check_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_check_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_uncheck_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_invert(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_invert(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_list_check_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_check_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_uncheck_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_invert(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_invert(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def list_widget_check_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(True) + + def list_widget_uncheck_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(False) + + def list_widget_invert(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(not check_box.isChecked()) + + def update_analyse_window(self): + selected_feature_dict = self.get_back_end_selected_feature_dict() + self.ui_dialog.textBrowser_analyse.clear() + neighbor_duration_information, neighbor_change_information = self.model_view.get_feature_rag_non_oxygen_neigbhbor_information( + self.selected_feature_classes, selected_feature_dict) + text_browser_input_text = "" + for feature_class_name, feature_class_information in neighbor_duration_information.items(): + if len(feature_class_information) > 0: + text_browser_input_text += "Feature Class {}:\n".format(feature_class_name) + for feature_index, feature_index_information in feature_class_information.items(): + text_browser_input_text += "Feature Index {}:\n".format(feature_index) + text_browser_input_text += "Duration \n" + for value, duration in feature_index_information.items(): + text_browser_input_text += "non oxygen value {}: {} duration \n".format(value, duration) + text_browser_input_text += "Direction \n" + change_direction_dict = Counter(neighbor_change_information[feature_class_name][feature_index]) + for direction, occurrences in change_direction_dict.items(): + text_browser_input_text += "From {} to {}, {}: {} occurrences \n".format(*direction, occurrences) + text_browser_input_text += "\n\n" + text_browser_input_text += "\n\n" + self.ui_dialog.textBrowser_analyse.setText(text_browser_input_text) + + def handle_export_dialog(self): + file_path = self.model_view.get_config_by_key_sub_key("feature_rag", "export_file_path") + if file_path is None or file_path == "": + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', file_path, "All Files(*.*)") + if file_path != "": + selected_feature_dict = self.get_back_end_selected_feature_dict() + self.model_view.export_feature_rag_information(file_path, self.selected_feature_classes, + selected_feature_dict) + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/feature_rag_visualization/__init__.py b/view/feature_rag_visualization/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_rag_visualization/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_rag_visualization/matplotlib/__init__.py b/view/feature_rag_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_rag_visualization/matplotlib/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_rag_visualization/matplotlib/feature_rag_graph_visualization.py b/view/feature_rag_visualization/matplotlib/feature_rag_graph_visualization.py new file mode 100644 index 0000000..6e3ee21 --- /dev/null +++ b/view/feature_rag_visualization/matplotlib/feature_rag_graph_visualization.py @@ -0,0 +1,140 @@ + + +import matplotlib.cm +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + +from model_view import frontend_adapter + + +class RagGraphVisualization(QtCore.QObject): + left_click_position = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.axes = self.figure.add_subplot(111) + self.set_up_axis_and_custom_stuff() + self.canvas.mpl_connect("key_press_event", self.key_pressed) + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def set_up_axis_and_custom_stuff(self): + max_number_index = self.model_view.get_feature_rag_number_of_image() + y_lim = [0, 1050] + self.axes.set_xlim([0, max_number_index]) + self.axes.set_ylim(y_lim) + self.draw_horizontal_areas(y_lim) + y_ticks_values, y_ticks_text = [], [] + for i in range(7): + y_ticks_values.append(i * 150 + 75) + y_ticks_text.append("{} Vacancies".format(i)) + self.axes.set_yticks(y_ticks_values) + self.axes.set_yticklabels(y_ticks_text) + self.generate_secondary_x_axis() + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, selected_image_number, selected_feature_classes, selected_feature_dict): + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.render_graph(selected_feature_classes, selected_feature_dict) + self.render_selected_image_area(selected_image_number) + self.set_up_axis_and_custom_stuff() + self.update() + + def image_number_to_time_stamp(self, image_number_list): + time_stamps = self.model_view.get_time_stamps() + time_stamps_represnetation_list = [] + for image_number in image_number_list: + try: + time_stamps_represnetation_list.append("{:4g}".format(time_stamps[image_number])) + except IndexError: + time_stamps_represnetation_list.append("") + return time_stamps_represnetation_list + + def render_selected_image_area(self, selected_image_number): + self.axes.axvspan(selected_image_number - 1, selected_image_number, color="red", alpha=0.4) + + def generate_secondary_x_axis(self): + number_of_images_with_rag = self.model_view.get_feature_rag_number_of_image() - 1 + new_tick_image_numbers = [] + for i in range(11): + new_tick_image_numbers.append(int(i * number_of_images_with_rag / 10)) + self.axes.set_xlabel("Image Number") + ax2 = self.axes.twiny() + ax2.set_xlim(self.axes.get_xlim()) + ax2.set_xticks(new_tick_image_numbers) + ax2.set_xticklabels(self.image_number_to_time_stamp(new_tick_image_numbers)) + ax2.set_xlabel("Time") + + def draw_horizontal_areas(self, y_lim): + index = y_lim[0] // 150 + start = (y_lim[0] // 150) * 150 + end = (y_lim[1] // 150) * 150 + current_area = start + color_map = matplotlib.cm.get_cmap("plasma") + while True: + line = self.axes.axhline(current_area, color='black') + line.set_linewidth(3) + color = color_map(index / 7) + self.axes.axhspan(current_area, current_area + 150, color=color, alpha=0.2) + current_area += 150 + index += 1 + if current_area > end: + break + + def generate_y_lim(self, ylim_prev): + y_lim = [ylim_prev[0] - 150] + if y_lim[0] < 0: + y_lim[0] = 0 + y_lim.append(ylim_prev[1] + 150) + return y_lim + + def render_graph(self, selected_feature_classes, selected_feature_dict): + if len(selected_feature_classes) == 0: + return + graph_dict = self.model_view.get_non_oxygen_feature_class_neighbor_values(selected_feature_classes, + selected_feature_dict) + line_list = [] + feature_class_offset = 100 / len(selected_feature_classes) + for class_counter, (feature_class_name, feature_graph_dict) in enumerate(graph_dict.items()): + if len(feature_graph_dict) == 0: + break + feature_offset = feature_class_offset / len(feature_graph_dict) + for feature_counter, (feature_index, line_points) in enumerate(feature_graph_dict.items()): + line_points = [ + line_point * 150 + 25 + feature_class_offset * class_counter + feature_offset * feature_counter for + line_point in line_points] + line_list.append(line_points) + for line_points in line_list: + self.axes.step(range(len(line_points)), line_points) + + def reset_render_window(self): + self.update() + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.xdata, event.ydata]) + + def key_pressed(self, event): + pass diff --git a/view/feature_rag_visualization/vtk/__init__.py b/view/feature_rag_visualization/vtk/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/feature_rag_visualization/vtk/__init__.py @@ -0,0 +1 @@ + diff --git a/view/feature_rag_visualization/vtk/feature_rag_visualization.py b/view/feature_rag_visualization/vtk/feature_rag_visualization.py new file mode 100644 index 0000000..17268e1 --- /dev/null +++ b/view/feature_rag_visualization/vtk/feature_rag_visualization.py @@ -0,0 +1,180 @@ + + +import vtk +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.image_visualization.vtk import qt_vtk_interactor +from view.image_visualization.vtk.rendering_methods import vtk_single_image + + +class FeatureRagWindow: + def __init__(self, widget, model_view): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.image_renderer = vtk.vtkRenderer() + self.rag_renderer = vtk.vtkRenderer() + image_camera = self.image_renderer.GetActiveCamera() # type: vtk.vtkCamera + # SET VTK (0,0) POINT TO TOP LEFT + image_camera.SetFocalPoint(0, 0, 0) + image_camera.SetPosition(0, 0, -1) + image_camera.SetViewUp(0, -1, 0) + self.rag_renderer.SetActiveCamera(image_camera) + self.image_renderer.ResetCamera() + self.mask = None + if widget is not None: + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + self.render_window = self.vtk_widget.GetRenderWindow() + self.vtk_widget.set_active_renderer(self.image_renderer) + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + else: + self.render_window = vtk.vtkRenderWindow() + self.render_window.Start() + self.render_window.AddRenderer(self.image_renderer) + self.render_window.AddRenderer(self.rag_renderer) + self.render_window.SetNumberOfLayers(2) + self.image_renderer.SetLayer(0) + self.rag_renderer.SetLayer(1) + + def set_background_color(self, background_color): + self.render_window.GetRenderers().GetFirstRenderer().SetBackground(background_color[:3]) + + def _get_image_actor(self, image_number, mask=None): + image = self.model_view.get_2d_image_by_number(image_number) + nan_color = self.model_view.get_nan_color() + contrast_settings = self.model_view.get_contrast_settings() + step_size = self.model_view.get_step_size() + image_actor = vtk_single_image.generate_2d_image_actor(image=image, + nan_color=nan_color, + contrast_settings=contrast_settings, step_size=step_size, + mask=mask) + return image_actor + + def render(self, image_number, selected_feature_classes, selected_feature_dict): + node_radius = 2 + self.image_renderer.RemoveAllViewProps() + self.rag_renderer.RemoveAllViewProps() + image_actor = self._get_image_actor(image_number=image_number) + self.render_rag_edges(image_number) + self.render_rag_nodes(image_number, node_radius, selected_feature_classes, selected_feature_dict) + self.image_renderer.AddActor(image_actor) + self.update() + + def reset_render_window(self): + self.image_renderer.RemoveAllViewProps() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.image_renderer.ResetCamera() + + def render_rag_nodes(self, image_number, node_radius, selected_feature_classes, selected_feature_dict): + step_size = self.model_view.get_step_size() + node_radius = node_radius * step_size[0] + node_dict = self.model_view.get_feature_rag_node_dict(image_number=image_number) + node_actor_dict = self.generate_node_actor_dict(node_dict, node_radius, selected_feature_classes, + selected_feature_dict) + for feature_class_name, actor in node_actor_dict.items(): + if feature_class_name == "selected": + color = [0, 0.8, 1] + else: + color = self.model_view.get_feature_class_color_by_name(feature_class_name) + color = [c / 255 for c in color] + actor.GetProperty().SetColor(*color[:3]) + self.rag_renderer.AddActor(actor) + + def generate_selected_node_dict(self, node_dict, selected_feature_class, selected_feature_dict): + node_dict["selected"] = [] + for key, point_list in node_dict.items(): + if key not in selected_feature_class: + continue + for point_index, point in enumerate(point_list): + if point_index in selected_feature_dict[key]: + node_dict["selected"].append(point) + return node_dict + + def generate_node_actor_dict(self, node_dict, node_radius, selected_feature_classes, selected_feature_dict): + node_poly_data_dict = {} + node_actor_dict = {} + node_dict = self.generate_selected_node_dict(node_dict, selected_feature_classes, selected_feature_dict) + for key, point_list in node_dict.items(): + node_poly_data_dict[key] = self.generate_poly_data_from_point_list(key, point_list, + selected_feature_classes, + selected_feature_dict) + for key, vtk_poly_data in node_poly_data_dict.items(): + vtk_source = self.generate_sphere_vtk_source(node_radius) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + node_actor_dict[key] = actor + return node_actor_dict + + def render_rag_edges(self, image_number, edge_radius=0.5): + step_size = self.model_view.get_step_size() + edge_radius = edge_radius * step_size[0] + edge_list = self.model_view.get_feature_rag_edge_list(image_number=image_number) + edge_actor = self.generate_edge_actor(edge_list, edge_radius) + self.rag_renderer.AddActor(edge_actor) + + def generate_edge_actor(self, edge_list, edge_radius): + vtk_poly_data = self.generate_line_poly_data(edge_list) + tube_filter = vtk.vtkTubeFilter() + tube_filter.SetInputData(vtk_poly_data) + tube_filter.SetRadius(edge_radius) + tube_filter.SetNumberOfSides(50) + tube_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(tube_filter.GetOutputPort()) + mapper.Update() + actor = vtk.vtkActor() + actor.SetMapper(mapper) + return actor + + def generate_poly_data_from_point_list(self, key, point_list, selected_feature_classes, selected_feature_dict): + vtk_points = vtk.vtkPoints() + for point in point_list: + vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_poly_data = vtk.vtkPolyData() + vtk_poly_data.SetPoints(vtk_points) + return vtk_poly_data + + def generate_sphere_vtk_source(self, radius): + sphere_source = vtk.vtkSphereSource() + sphere_source.SetRadius(radius) + sphere_source.Update() + return sphere_source + + def generate_line_poly_data(self, line_list): + vtk_polydata = vtk.vtkPolyData() + vtk_points = vtk.vtkPoints() + vtk_lines = vtk.vtkCellArray() + point_counter = 0 + for line in line_list: + lines = vtk.vtkLine() + lines.GetPointIds().SetNumberOfIds(len(line)) # make a quad + lines_counter = 0 + for point in line: + vtk_points.InsertNextPoint(point[0], point[1], 0) + lines.GetPointIds().SetId(lines_counter, point_counter) + lines_counter += 1 + point_counter += 1 + vtk_lines.InsertNextCell(lines) + vtk_polydata.SetPoints(vtk_points) + vtk_polydata.SetLines(vtk_lines) + return vtk_polydata diff --git a/view/feature_visualization/__init__.py b/view/feature_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/filter_dialog_1d.py b/view/filter_dialog_1d.py new file mode 100644 index 0000000..b7ef0d8 --- /dev/null +++ b/view/filter_dialog_1d.py @@ -0,0 +1,211 @@ +import logging + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.image_visualization import qt_render_window_factory +from view.ui import ui_dialog_filter_1d + +log = logging.getLogger(__name__) + + +class FilterDialog1D(QtWidgets.QDialog): + def __init__(self, model_view, filter_type="1d", parent=None, maximum_number_of_points=0): + QtWidgets.QDialog.__init__(self, parent) + log.info("Open 1D Filter Dialog") + self.filter_type = filter_type + self.maximum_number_of_points = maximum_number_of_points + self.min_amplitude = None + self.max_amplitude = None + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_filter_1d.Ui_Dialog_filter() + self.ui_dialog.setupUi(self) + self.generate_render_windows() + self.update_ui() + filter_list = model_view.get_available_1d_filter_list() + for filter in filter_list: + self.ui_dialog.comboBox_filter_choser.addItem(filter.__str__()) + self.ui_dialog.pushButton_delete_filter.clicked.connect(self.remove_filter_from_list_widget) + self.ui_dialog.pushButton_add_filter.clicked.connect(self.add_filter_to_list_widget) + self.ui_dialog.pushButton_apply_filter_test.clicked.connect(self.apply_filters_on_images) + self.ui_dialog.comboBox_filter_choser.currentIndexChanged.connect( + self.create_input_fields_for_combobox_item) + self.create_input_fields_for_combobox_item() + self.setWindowState(QtCore.Qt.WindowMaximized) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.selected_points_interactor_widgets = list() + + def generate_render_windows(self): + factory = qt_render_window_factory.RenderWindowFactory + normal_window_type = self.model_view.get_config_by_key_sub_key('program_settings', '1d_filter_image') + self.render_window_normal = factory.get_render_window(normal_window_type)( + widget=self.ui_dialog.widget_vtk_filter_preview, model_view=self.model_view) + signal_window_type = self.model_view.get_config_by_key_sub_key('program_settings', '1d_filter_signal') + self.render_window_signal = factory.get_render_window(signal_window_type, is_chart=True)( + widget=self.ui_dialog.widget_vtk_filter_1d_signal, + model_view=self.model_view, fourier_transformed=False) + fft_window_type = self.model_view.get_config_by_key_sub_key('program_settings', '1d_filter_fft') + self.render_window_fft = factory.get_render_window(fft_window_type, is_chart=True)( + widget=self.ui_dialog.widget_vtk_filter_fft, + model_view=self.model_view, fourier_transformed=True) + self.render_window_fft.selected_points_signal.connect(self.points_selected) + + def add_filter_to_list_widget(self): + input_dict = self.model_view.get_1d_filters_inputs(self.ui_dialog.comboBox_filter_choser.currentIndex()) + input_parameter = list() + for i in range(self.ui_dialog.gridLayout_filter_inputs.count()): + layout_item = self.ui_dialog.gridLayout_filter_inputs.itemAt(i).widget() + object_name = layout_item.objectName() + if object_name in input_dict: + if type(layout_item) == QtWidgets.QListWidget: + list_items = self.get_list_items_as_int(layout_item) + input_parameter.append(list_items) + elif type(layout_item) == QtWidgets.QLabel: + input_parameter.append(float(layout_item.text())) + else: + input_parameter.append(layout_item.value()) + + self.model_view.add_1d_filter_to_current_scan(self.ui_dialog.comboBox_filter_choser.currentIndex(), + parameters=input_parameter, filter_type=self.filter_type) + self.ui_dialog.listWidget_filter_list.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(filter_type=self.filter_type): + self.ui_dialog.listWidget_filter_list.addItem(filter_str) + + def get_list_items_as_int(self, list_widget): # + all_items = list() + for i in range(list_widget.count()): + all_items.append(float(list_widget.item(i).text())) + return all_items + + def remove_filter_from_list_widget(self): + selected_items = self.ui_dialog.listWidget_filter_list.selectionModel().selectedIndexes() + for item in selected_items: + self.model_view.remove_1d_filter_from_current_scan(item.row(), filter_type=self.filter_type) + self.update_filter_list_widget() + + def apply_filters_on_images(self): + self.model_view.apply_1d_filter_current_scan(filter_type=self.filter_type) + self.update_ui() + + def remove_items_from_layout(self, layout): + for i in reversed(range(layout.count())): + layout.itemAt(i).widget().setParent(None) + + def get_data_type(self, value): + try: + return value[0] + except: + return None + + def get_start_value(self, value): + try: + return value[1] + except: + return None + + def create_input_fields_for_combobox_item(self): + input_dict = self.model_view.get_1d_filters_inputs(self.ui_dialog.comboBox_filter_choser.currentIndex()) + layout = self.ui_dialog.gridLayout_filter_inputs + self.remove_items_from_layout(layout) + filter_str = self.ui_dialog.comboBox_filter_choser.currentText() + row = 0 + self.selected_points_interactor_widgets = list() + for key, parameters in input_dict.items(): + label = QtWidgets.QLabel(key) + layout.addWidget(label, row, 0) + data_type = self.get_data_type(value=parameters) + start_value = self.get_start_value(value=parameters) + if data_type in ["int", "double"]: + widget = self.generate_spin_box(filter_str=filter_str, key=key, spinbox_type=data_type, + start_value=start_value) + elif data_type in ["selected_point_list"]: + widget = self.generate_list_widget(key=key, list_type=data_type, start_value=start_value) + elif data_type in ["label"]: + widget = self.generate_label(key=key, identifier=start_value) + layout.addWidget(widget, row, 1) + row += 1 + + def generate_list_widget(self, key, list_type, start_value): + if list_type == "selected_point_list": + list_widget = QtWidgets.QListWidget() + self.selected_points_interactor_widgets = list_widget + list_widget.setObjectName(key) + return list_widget + + def generate_label(self, key, identifier): + if identifier == "sample_frequency": + sample_frequency = self.model_view.get_freqeuncy_samlping() + label_widget = QtWidgets.QLabel(str(sample_frequency)) + label_widget.setObjectName(key) + else: + label_widget = None + + return label_widget + + def points_selected(self, point_list): + if self.ui_dialog.comboBox_filter_choser.currentText() == "Frequency Remover": + self.selected_points_interactor_widgets.clear() + self.selected_points_interactor_widgets.addItems([str(point[0]) for point in point_list]) + elif self.ui_dialog.comboBox_filter_choser.currentText() == "Frequency Manipulator": + try: + point = point_list[-1] + except IndexError: + return + self.selected_points_interactor_widgets[0].setValue(int(str(point[0]))) + self.selected_points_interactor_widgets[1].setValue(float(str(point[1]))) + + def generate_spin_box(self, filter_str, key, spinbox_type, start_value): + if spinbox_type == "int": + spin_box = QtWidgets.QSpinBox() + elif spinbox_type == "double": + spin_box = QtWidgets.QDoubleSpinBox() + else: + log.exception("Datatype was not correctly defined") + spin_box = QtWidgets.QSpinBox() + if isinstance(start_value, str): + if start_value == "min_amplitude": + start_value = self.model_view.get_min_amplitude(0) + elif start_value == "max_amplitude": + start_value = self.model_view.get_max_amplitude(0) + + spin_box.setMaximum(2 ** 30) + spin_box.setMinimum(-2 ** 30) + spin_box.setValue(start_value or 0) + if filter_str == "Frequency Manipulator": + if key in ["Frequency", "Value"]: + self.selected_points_interactor_widgets.append(spin_box) + + spin_box.setObjectName(key) + return spin_box + + def update_ui(self): + self.update_image_window() + self.update_fft_window() + self.update_1d_signal_window() + + def update_image_window(self): + self.render_window_normal.render(scan_index=None, image_number=0) + self.render_window_normal.reset_render_camera() + self.update_filter_list_widget() + self.render_window_normal.update() + + def update_fft_window(self): + self.render_window_fft.render(scan_index=None, image_number=0, filter_type=self.filter_type, + maximum_number_of_points=self.maximum_number_of_points) + self.render_window_fft.update() + + def update_1d_signal_window(self): + self.render_window_signal.render(scan_index=None, image_number=0, filter_type=self.filter_type, + maximum_number_of_points=self.maximum_number_of_points) + self.render_window_signal.update() + + def update_filter_list_widget(self): + index = self.ui_dialog.listWidget_filter_list.currentRow() + self.ui_dialog.listWidget_filter_list.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(filter_type=self.filter_type): + self.ui_dialog.listWidget_filter_list.addItem(filter_str) + number_of_items = self.ui_dialog.listWidget_filter_list.count() + if index >= number_of_items: + self.ui_dialog.listWidget_filter_list.setCurrentRow(number_of_items - 1) + else: + self.ui_dialog.listWidget_filter_list.setCurrentRow(index) diff --git a/view/filter_dialog_2d.py b/view/filter_dialog_2d.py new file mode 100644 index 0000000..435e2c2 --- /dev/null +++ b/view/filter_dialog_2d.py @@ -0,0 +1,295 @@ +import logging + +from PyQt5 import QtWidgets, QtCore +from pyqode.core import modes, api + +from model_view import frontend_adapter +from view.image_visualization import qt_render_window_factory +from view.ui import ui_dialog_filter_2d + +log = logging.getLogger(__name__) + + +class FilterDialog2D(QtWidgets.QDialog): + def __init__(self, model_view, scan_test_filter, parent=None): + QtWidgets.QDialog.__init__(self, parent) + log.info("Open 2D Filter Dialog") + self.min_amplitude = None + self.max_amplitude = None + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_filter_2d.Ui_Dialog_filter() + self.ui_dialog.setupUi(self) + self.generate_render_windows() + self.image_left_click_callback = None + self.image_right_click_callback = None + self.current_list_widget: QtWidgets.QListWidget = None + # ___________________________________________ + self.update_ui() + filter_list = model_view.get_available_2d_filter_list() + for filter in filter_list: + self.ui_dialog.comboBox_filter_choser.addItem(filter.__str__()) + self.set_up_saved_filter_widget() + self.ui_dialog.pushButton_saved_filter.clicked.connect(self.handle_save_filter_button) + self.ui_dialog.comboBox_saved_filter.currentIndexChanged.connect(self.handle_saved_filter_changed) + self.ui_dialog.pushButton_delete_filter.clicked.connect(self.remove_filter_from_list_widget) + self.ui_dialog.pushButton_add_filter.clicked.connect(self.add_filter_to_list_widget) + self.ui_dialog.pushButton_apply_filter_test.clicked.connect(self.apply_filters_on_images) + self.ui_dialog.comboBox_filter_choser.currentIndexChanged.connect( + self.handle_filter_changed) + self.ui_dialog.comboBox_filter_choser.currentIndexChanged.connect(self.update_ui) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.model_view.apply_2d_filter_current_scan() + + def generate_render_windows(self): + factory = qt_render_window_factory.RenderWindowFactory + main_window_type = self.model_view.get_config_by_key_sub_key('program_settings', '2d_filter_image') + self.image_render_window = factory.get_render_window(main_window_type)( + widget=self.ui_dialog.widget_vtk_filter_preview, model_view=self.model_view) + second_window_type = self.model_view.get_config_by_key_sub_key('program_settings', '2d_filter_fft') + self.fft_render_window = factory.get_render_window(second_window_type)( + widget=self.ui_dialog.widget_vtk_filter_fft, + model_view=self.model_view, fourier_transformed=True) + self.image_render_window.activate_click_signals() + self.image_render_window.left_click_position_signal.connect(self.image_left_clicked) + self.image_render_window.right_click_position_signal.connect(self.image_right_clicked) + + def add_filter_to_list_widget(self): + input_dict = self.model_view.get_2d_filters_inputs(self.ui_dialog.comboBox_filter_choser.currentIndex()) + input_parameter = list() + for i in range(self.ui_dialog.gridLayout_filter_inputs.count()): + layout_item = self.ui_dialog.gridLayout_filter_inputs.itemAt(i).widget() + object_name = layout_item.objectName() + class_name = layout_item.__class__.__name__ + if object_name in input_dict: + if "spinbox" in class_name.lower(): + input_parameter.append(layout_item.value()) + elif "plaintext" in class_name.lower(): + input_parameter.append(layout_item.toPlainText()) + elif "codeedit" in class_name.lower(): + input_parameter.append(layout_item.toPlainText()) + elif "listwidget" in class_name.lower(): + list_widget_item_list = [] + for i in range(layout_item.count()): + list_widget_item_list.append(layout_item.item(i).text()) + input_parameter.append(list_widget_item_list) + self.model_view.add_2d_filter_to_current_scan(self.ui_dialog.comboBox_filter_choser.currentIndex(), + parameters=input_parameter) + self.ui_dialog.listWidget_filter_list.clear() + for filter_str in self.model_view.get_2d_filter_str_filter_list(): + self.ui_dialog.listWidget_filter_list.addItem(filter_str) + + def remove_filter_from_list_widget(self): + selected_items = self.ui_dialog.listWidget_filter_list.selectionModel().selectedIndexes() + for item in selected_items: + self.model_view.remove_2d_filter_from_current_scan(item.row()) + self.update_filter_list_widget() + + def apply_filters_on_images(self): + self.model_view.apply_2d_filter_current_scan() + self.update_ui() + + def remove_items_from_layout(self, layout): + for i in reversed(range(layout.count())): + layout.itemAt(i).widget().setParent(None) + + def get_data_type(self, value): + try: + return value[0] + except: + return None + + def get_start_value(self, value): + try: + return value[1] + except: + return None + + def create_spin_box(self, data_type, start_value): + if data_type == "int": + spin_box = QtWidgets.QSpinBox() + elif data_type == "double": + spin_box = QtWidgets.QDoubleSpinBox() + spin_box.setDecimals(5) + if isinstance(start_value, str): + if start_value == "min_amplitude": + start_value = self.model_view.get_min_amplitude(0) + elif start_value == "max_amplitude": + start_value = self.model_view.get_max_amplitude(0) + spin_box.setMaximum(2 ** 30) + spin_box.setMinimum(-2 ** 30) + spin_box.setValue(start_value or 0) + spin_box.valueChanged.connect(self.update_ui) + return spin_box + + def generate_point_list_widget(self): + list_widget = QtWidgets.QListWidget() + self.current_list_widget = list_widget + self.image_left_click_callback = self.add_to_current_list_widget + self.image_right_click_callback = self.remove_last_from_current_list_widget + return list_widget + + def create_code_widget(self, data_type, start_value): + if data_type == "code": + code_widget = api.CodeEdit() + code_widget.setPlainText(start_value, mime_type='', encoding="utf-8") + code_widget.modes.append(modes.PygmentsSyntaxHighlighter(code_widget.document())) + code_widget.modes.append(modes.CaretLineHighlighterMode()) + else: + code_widget = None + return code_widget + + def set_up_saved_filter_widget(self): + self.update_saved_filter_widget() + + def update_saved_filter_widget(self): + filter_class = self.ui_dialog.comboBox_filter_choser.currentText() + filter_names = self.model_view.get_saved_filter_names(filter_category="2d", filter_class=filter_class) + filter_names = ["None", *filter_names] + index = self.ui_dialog.comboBox_saved_filter.currentIndex() + self.ui_dialog.comboBox_saved_filter.clear() + self.ui_dialog.comboBox_saved_filter.addItems(filter_names) + if index > -1: + self.ui_dialog.comboBox_saved_filter.setCurrentIndex(index) + + def handle_filter_changed(self): + self.update_saved_filter_widget() + self.create_input_fields_for_combobox_item() + + def create_input_fields_for_combobox_item(self): + input_dict = self.model_view.get_2d_filters_inputs(self.ui_dialog.comboBox_filter_choser.currentIndex()) + layout = self.ui_dialog.gridLayout_filter_inputs + self.remove_items_from_layout(layout) + self.set_up_filter_inputs(layout, input_dict, row=0) + + def update_ui(self): + self.update_image_window() + self.update_fft_window() + + def update_image_window(self): + self.image_render_window.render(scan_index=None, image_number=0) + self.image_render_window.reset_render_camera() + self.ui_dialog.listWidget_filter_list.clear() + self.update_filter_list_widget() + + def update_fft_window(self): + current_filter_class_index = self.ui_dialog.comboBox_filter_choser.currentIndex() + input_dict = self.model_view.get_2d_filters_inputs(current_filter_class_index) + input_parameter = list() + for i in range(self.ui_dialog.gridLayout_filter_inputs.count()): + layout_item = self.ui_dialog.gridLayout_filter_inputs.itemAt(i).widget() + object_name = layout_item.objectName() + class_name = layout_item.__class__.__name__ + if object_name in input_dict: + if "spinbox" in class_name.lower(): + input_parameter.append(layout_item.value()) + elif "plaintext" in class_name.lower(): + input_parameter.append(layout_item.toPlainText()) + elif "codeedit" in class_name.lower(): + input_parameter.append(layout_item.toPlainText()) + + mask = self.model_view.get_filter_mask(filter_class_index=current_filter_class_index, + parameters=input_parameter) + self.fft_render_window.render(scan_index=None, image_number=0, mask=mask) + self.fft_render_window.reset_render_camera() + + def handle_saved_filter_changed(self): + filter_class = self.ui_dialog.comboBox_filter_choser.currentText() + filter_name = self.ui_dialog.comboBox_saved_filter.currentText() + if filter_name == "None": + return + self.ui_dialog.lineEdit_saved_filter_name.setText(filter_name) + input_dict = self.model_view.get_saved_filter_dict(filter_category="2d", filter_class=filter_class, + filter_name=filter_name) + self.set_filter_inputs_by_filter_input_dict(input_dict) + + def handle_save_filter_button(self): + save_filter_name = self.ui_dialog.lineEdit_saved_filter_name.text() + input_dict = self.get_filter_input_dict() + filter_class = self.ui_dialog.comboBox_filter_choser.currentText() + self.model_view.add_filter_to_saved_filters(filter_category="2d", filter_class=filter_class, + filter_name=save_filter_name, + filter_inputs=input_dict) + self.ui_dialog.comboBox_saved_filter.setCurrentIndex(-1) + self.update_saved_filter_widget() + self.ui_dialog.comboBox_saved_filter.setCurrentIndex(self.ui_dialog.comboBox_saved_filter.count() - 1) + + def update_filter_list_widget(self): + index = self.ui_dialog.listWidget_filter_list.currentRow() + self.ui_dialog.listWidget_filter_list.clear() + for filter_str in self.model_view.get_2d_filter_str_filter_list(): + self.ui_dialog.listWidget_filter_list.addItem(filter_str) + number_of_items = self.ui_dialog.listWidget_filter_list.count() + if index >= number_of_items: + self.ui_dialog.listWidget_filter_list.setCurrentRow(number_of_items - 1) + else: + self.ui_dialog.listWidget_filter_list.setCurrentRow(index) + + def set_up_filter_inputs(self, layout, input_dict, row): + self.current_list_widget = None + for key, value in input_dict.items(): + label = QtWidgets.QLabel(key) + layout.addWidget(label, row, 0) + data_type = self.get_data_type(value=value) + start_value = self.get_start_value(value=value) + input_widget = None + if data_type == "int" or data_type == "double": + input_widget = self.create_spin_box(data_type=data_type, start_value=start_value) + input_widget.valueChanged.connect(self.update_ui) + elif data_type == "code": + input_widget = self.create_code_widget(data_type, start_value) + elif data_type == "selected_point_list": + input_widget = self.generate_point_list_widget() + input_widget.setObjectName(key) + layout.addWidget(input_widget, row, 1) + row += 1 + + def get_filter_input_dict(self): + input_dict = {} + for i in range(self.ui_dialog.gridLayout_filter_inputs.count()): + layout_item = self.ui_dialog.gridLayout_filter_inputs.itemAt(i).widget() + object_name = layout_item.objectName() + class_name = layout_item.__class__.__name__ + if "spinbox" in class_name.lower(): + input_dict[object_name] = layout_item.value() + elif "plaintext" in class_name.lower(): + input_dict[object_name] = layout_item.toPlainText() + return input_dict + + def set_filter_inputs_by_filter_input_dict(self, input_dict): + for i in range(self.ui_dialog.gridLayout_filter_inputs.count()): + layout_item = self.ui_dialog.gridLayout_filter_inputs.itemAt(i).widget() + object_name = layout_item.objectName() + class_name = layout_item.__class__.__name__ + if object_name in input_dict: + if "spinbox" in class_name.lower(): + layout_item.setValue(input_dict[object_name]) + elif "plaintext" in class_name.lower(): + layout_item.setPlainText(input_dict[object_name]) + elif "codeedit" in class_name.lower(): + layout_item.setPlainText(input_dict[object_name], mime_type='', encoding="utf-8") + + return input_dict + + def add_to_current_list_widget(self, point): + if self.current_list_widget is not None: + point_in_pixels = self.model_view.scale_position_from_frontend_to_backend(point) + self.current_list_widget.addItem(str(point_in_pixels)) + + def remove_last_from_current_list_widget(self, *args, **kwargs): + if self.current_list_widget is not None: + try: + self.current_list_widget.takeItem(self.current_list_widget.count() - 1) + except: + pass + + def image_left_clicked(self, coordinates): + if self.image_left_click_callback is None: + pass + else: + self.image_left_click_callback(coordinates) + + def image_right_clicked(self, coordinates): + if self.image_right_click_callback is None: + pass + else: + self.image_right_click_callback(coordinates) diff --git a/view/image_visualization/__init__.py b/view/image_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/colors/__init__.py b/view/image_visualization/colors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/colors/feature_class_colors.py b/view/image_visualization/colors/feature_class_colors.py new file mode 100644 index 0000000..b1eeb2c --- /dev/null +++ b/view/image_visualization/colors/feature_class_colors.py @@ -0,0 +1,54 @@ +import random + +kelly_colors = ['F2F3F4', '222222', 'F3C300', '875692', 'F38400', 'A1CAF1', 'BE0032', 'C2B280', '848482', '008856', + 'E68FAC', '0067A5', 'F99379', '604E97', 'F6A600', 'B3446C', 'DCD300', '882D17', '8DB600', '654522', + 'E25822', '2B3D26'] + +color_list = [(243, 195, 0), + (135, 86, 146), + (243, 132, 0), + (161, 202, 241), + (190, 0, 50), + (194, 178, 128), + (132, 132, 130), + (0, 136, 86), + (230, 143, 172), + (0, 103, 165), + (249, 147, 121), + (96, 78, 151), + (246, 166, 0), + (179, 68, 108), + (220, 211, 0), + (136, 45, 23), + (141, 182, 0), + (101, 69, 34), + (226, 88, 34), + (43, 61, 38),] + +color_class_name_dict = {} + +def __generate_random_color(): + r = random.randint(0, 255) + g = random.randint(0, 255) + b = random.randint(0, 255) + random_color = (r, g, b) + return random_color + + +def get_color_by_index(index): + if index >= len(color_list): + random_color = __generate_random_color() + while random_color in color_list: + random_color = __generate_random_color() + color_list.append(random_color) + return color_list[index] + +def set_color_by_class_name(class_name, color): + color_class_name_dict['class_name'] = color + +def get_color_by_class_name(class_name): + if color_class_name_dict[class_name] in color_class_name_dict: + return color_class_name_dict[class_name] + else: + return (255,0,0) + diff --git a/view/image_visualization/matplotlib/__init__.py b/view/image_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/matplotlib/qt_matplotlib_chart_render_window.py b/view/image_visualization/matplotlib/qt_matplotlib_chart_render_window.py new file mode 100644 index 0000000..44406ba --- /dev/null +++ b/view/image_visualization/matplotlib/qt_matplotlib_chart_render_window.py @@ -0,0 +1,74 @@ +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.path import Path +from matplotlib.pyplot import Figure +from matplotlib.widgets import LassoSelector + +from view.image_visualization.matplotlib.rendering_methods.matplot_chart_visualization import visualize_image_as_chart + + +class CustomQtMatplotlibChartRenderWindow(QtCore.QObject): + selected_points_signal = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view, fourier_transformed=False): + QtCore.QObject.__init__(self) + self.model_view = model_view + self.fourier_transformed = fourier_transformed + self.figure = Figure() + self.axis = self.figure.add_subplot(111) + + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + if self.model_view.get_config_by_key_sub_key('program_settings', 'matplotlib_toolbar'): + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.vl.addWidget(self.canvas) + widget.setLayout(self.vl) + self.lasso_selector = LassoSelector(self.axis, onselect=self.onselect) + self.figure.tight_layout() + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, scan_index, image_number, filter_type="1d", maximum_number_of_points=None): + self.axis.clear() + if filter_type == "1d_full_signal": + if self.fourier_transformed: + image = self.model_view.get_full_1d_signal_fourier_transformed(scan_index=scan_index) + else: + image = self.model_view.get_full_1d_signal(scan_index=scan_index) + else: + image = self.model_view.get_1d_signal_by_number(number=image_number, scan_index=None, + fourier_transformed=self.fourier_transformed) + frequency_sampling = self.model_view.get_freqeuncy_samlping(scan_index) + line_collection = visualize_image_as_chart(self.axis, image, fourier_transformed=self.fourier_transformed, + frequency_sampling=frequency_sampling, + maximum_number_of_points=maximum_number_of_points) + self.xys = np.asarray(line_collection[0].get_data()).T + self.update() + + def reset_render_window(self): + self.update() + + def onselect(self, verts): + path = Path(verts) + self.ind = np.nonzero(path.contains_points(self.xys))[0] + points = self.xys[self.ind] + self.axis.scatter(points[:, 0], points[:, 1], color=[0, 0, 0], marker="x") + self.selected_points_signal.emit(np.ndarray.tolist(points)) + self.canvas.draw_idle() + + def disconnect(self): + self.lasso.disconnect_events() + self.fc[:, -1] = 1 + self.collection.set_facecolors(self.fc) + self.canvas.draw_idle() diff --git a/view/image_visualization/matplotlib/qt_matplotlib_render_window.py b/view/image_visualization/matplotlib/qt_matplotlib_render_window.py new file mode 100644 index 0000000..1245119 --- /dev/null +++ b/view/image_visualization/matplotlib/qt_matplotlib_render_window.py @@ -0,0 +1,47 @@ +from PyQt5 import QtWidgets +from matplotlib.backends import backend_qt5agg +from matplotlib.pyplot import Figure + +from model_view import frontend_adapter +from view.image_visualization.matplotlib.rendering_methods import matplotlib_single_image + + +class CustomQtMatplotlibRenderWindow(): + def __init__(self, widget, model_view, fourier_transformed=False, handle_features=False): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.fourier_transformed = fourier_transformed + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + if self.model_view.get_config_by_key_sub_key('program_settings', 'matplotlib_toolbar'): + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, image_number, scan_index=None): + image = self.model_view.get_2d_image_by_number(image_number, scan_index, + fourier_transformed=self.fourier_transformed, + logarithmic=self.fourier_transformed) + matplotlib_single_image.visualize_image_as_2d_histogram(self.figure, image=image) + self.update() + + def show_image(figure, image=None, mask=None): + figure.clear() + ax = figure.add_subplot(111) + image = image.astype(float) + ax.imshow(image) + + def reset_render_window(self): + self.update() diff --git a/view/image_visualization/matplotlib/rendering_methods/__init__.py b/view/image_visualization/matplotlib/rendering_methods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/matplotlib/rendering_methods/matplot_chart_visualization.py b/view/image_visualization/matplotlib/rendering_methods/matplot_chart_visualization.py new file mode 100644 index 0000000..5eb78d2 --- /dev/null +++ b/view/image_visualization/matplotlib/rendering_methods/matplot_chart_visualization.py @@ -0,0 +1,55 @@ +import numpy as np + +import numpy as np +import vtk +from vtk.util import numpy_support + + +def visualize_image_as_chart(axis, image, fourier_transformed, frequency_sampling, + maximum_number_of_points=None): + image = image.copy() + y_axis = image[:, 2] + if not fourier_transformed: + time_delta = 1 / frequency_sampling + numpy_array_x_axis = np.arange(image.shape[0]) * time_delta + if maximum_number_of_points is not None: + numpy_array_x_axis, y_axis = reduce_number_of_points_normal(x_axis=numpy_array_x_axis, y_axis=y_axis, + maximum_number_of_points=maximum_number_of_points) + else: + frequency_delta = frequency_sampling / image.shape[0] + frequency_delta = round(frequency_delta, 3) + numpy_array_x_axis = np.arange(image.shape[0]) * frequency_delta + numpy_array_x_axis -= (image.shape[0] / 2 * frequency_delta) + if maximum_number_of_points is not None: + numpy_array_x_axis, y_axis = reduce_number_of_points_fft(x_axis=numpy_array_x_axis, y_axis=y_axis, + maximum_number_of_points=maximum_number_of_points) + + return axis.plot(numpy_array_x_axis, y_axis) + + +def reduce_number_of_points_fft(x_axis, y_axis, maximum_number_of_points): + if y_axis.shape[0] <= maximum_number_of_points or maximum_number_of_points == 0: + return x_axis, y_axis + tmp_args = np.argpartition(-y_axis, maximum_number_of_points) + result_args = tmp_args[:maximum_number_of_points] + result_args.sort() + x_axis_clean = x_axis[result_args] + y_axis_clean = y_axis[result_args] + return x_axis_clean, y_axis_clean + + +def average(arr, n): + """stolen from https://stackoverflow.com/questions/10847660/subsampling-averaging-over-a-numpy-array/47066522""" + end = n * int(len(arr) / n) + return np.mean(arr[:end].reshape(-1, n), 1) + + +def reduce_number_of_points_normal(x_axis, y_axis, maximum_number_of_points): + if y_axis.shape[0] <= maximum_number_of_points or maximum_number_of_points == 0: + return x_axis, y_axis + bin_size = int(x_axis.shape[0] / maximum_number_of_points) + y_axis_clean = average(y_axis, bin_size) + number_of_bins = y_axis_clean.shape[0] + x_axis_clean = x_axis[:number_of_bins] + x_axis_clean *= bin_size + return x_axis_clean, y_axis_clean diff --git a/view/image_visualization/matplotlib/rendering_methods/matplotlib_single_image.py b/view/image_visualization/matplotlib/rendering_methods/matplotlib_single_image.py new file mode 100644 index 0000000..4ada3d4 --- /dev/null +++ b/view/image_visualization/matplotlib/rendering_methods/matplotlib_single_image.py @@ -0,0 +1,5 @@ +def visualize_image_as_2d_histogram(figure, image=None, mask=None): + figure.clear() + ax = figure.add_subplot(111) + image = image.astype(float) + ax.imshow(image) diff --git a/view/image_visualization/matplotlib/rendering_methods/visualize_as_image.py b/view/image_visualization/matplotlib/rendering_methods/visualize_as_image.py new file mode 100644 index 0000000..5983ad5 --- /dev/null +++ b/view/image_visualization/matplotlib/rendering_methods/visualize_as_image.py @@ -0,0 +1,105 @@ + + +import copy +import logging +import pickle + +import matplotlib.cm +import matplotlib.colors +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.collections import EllipseCollection + +log = logging.getLogger(__name__) + + +def export_as_image(file_path, model_view, background_color, image_number, + resolution, file_format, show_rag=False, show_features=False, show_scale=False): + if file_format == "pickle": + image = model_view.get_2d_image_by_number(image_number) + pickle.dump(image, open(file_path + ".pickle", "wb")) + return + fig = plt.figure() + axes = fig.add_subplot(111) + render_image(axes, model_view, image_number) + if show_features is True: + render_features(axes, model_view, image_number) + time_stamp_text = model_view.get_time_stamp_text(image_number) + image_class_name = model_view.get_scan_direction_by_image_number(image_number) + pre_text = model_view.get_config_by_key_sub_key("time_stamps", "pre_text") + title_str = "" + if image_class_name is not None and image_class_name != "None": + title_str += "Image Class: {}\n".format(image_class_name) + if time_stamp_text != "": + title_str += "{} {}".format(pre_text, time_stamp_text) + axes.set_title(title_str) + fig.savefig(file_path + "." + file_format, dpi=600) + + +def render_image(axes, model_view, image_number): + image = model_view.get_2d_image_by_number(number=image_number) + contrast_settings = model_view.get_contrast_settings() + nan_color = model_view.get_nan_color() + step_size = model_view.get_step_size() + if contrast_settings[0] == "percentile": + if np.isnan(image).all(): + contrast_range = None + else: + contrast_range = np.percentile(image[~np.isnan(image)], (contrast_settings[1], contrast_settings[2])) + elif contrast_settings[0] == "absolute": + contrast_range = (contrast_settings[1], contrast_settings[2]) + else: + contrast_range = None + cmap = copy.copy(matplotlib.cm.gray) + cmap.set_bad(nan_color) + if contrast_range is not None: + normalizer = matplotlib.colors.Normalize(vmin=contrast_range[0], vmax=contrast_range[1]) + else: + normalizer = None + axes.imshow(image, cmap=cmap, zorder=0, norm=normalizer, + extent=[0, len(image[0]) * step_size[0], len(image) * step_size[1], 0]) + plt.show() + + +def repair_nan_inside_of_image(image): + nan_indices = np.where(np.isnan(image)) + x_indices, y_indices = nan_indices + for x, y in zip(x_indices, y_indices): + try: + left_neighbor = image[x, y - 1] + right_neighbor = image[x, y + 1] + if not np.isnan(left_neighbor) and not np.isnan(right_neighbor): + image[x, y] = (left_neighbor + right_neighbor) / 2 + continue + except IndexError: + pass + + try: + bottom_neighbor = image[x - 1, y] + top_neighbor = image[x + 1, y] + if not np.isnan(bottom_neighbor) and not np.isnan(top_neighbor): + image[x, y] = (bottom_neighbor + top_neighbor) / 2 + except IndexError: + pass + + return image + + +def render_features(axes, model_view, image_number): + feature_dict = model_view.get_feature_dictionary(image_number) + for feature_class_name, feature_class in feature_dict.items(): + feature_class_points = [] + for feature_index, feature in feature_class.items(): + for point_id, point in enumerate(feature): + feature_class_points.append(point[:2]) + step_size = model_view.get_step_size() + node_radius_list = [step_size[0] * 2] * len(feature_class_points) + color = model_view.get_feature_class_color_by_name(feature_class_name) + color = [c / 255 for c in color] + circle_collection = EllipseCollection(node_radius_list, node_radius_list, + np.zeros_like(node_radius_list), + offsets=feature_class_points, units='x', + transOffset=axes.transData) + circle_collection.zorder = 1 + circle_collection.set_color(color) + axes.add_collection(circle_collection) diff --git a/view/image_visualization/qt_render_window.py b/view/image_visualization/qt_render_window.py new file mode 100644 index 0000000..0ce7b68 --- /dev/null +++ b/view/image_visualization/qt_render_window.py @@ -0,0 +1,25 @@ +import abc + + +class QtRenderWindow(abc.ABCMeta): + def update(self): + raise NotImplementedError + + def reset_render_camera(self): + raise NotImplementedError + + def render_image(self, image_number, scan_index=None): + raise NotImplementedError + + def render_feature_visualization(self): + raise NotImplementedError + + def render_text_actor(self): + raise NotImplementedError + + def render_all(self): + raise NotImplementedError + + def reset_render_window(self): + raise NotImplementedError + diff --git a/view/image_visualization/qt_render_window_factory.py b/view/image_visualization/qt_render_window_factory.py new file mode 100644 index 0000000..f138411 --- /dev/null +++ b/view/image_visualization/qt_render_window_factory.py @@ -0,0 +1,22 @@ +from view.image_visualization.matplotlib import qt_matplotlib_render_window, qt_matplotlib_chart_render_window +from view.image_visualization.vtk import qt_vtk_render_window, qt_vtk_render_chart_window + + +class RenderWindowFactory: + @classmethod + def get_render_window(cls, render_window_type_str, is_chart=False): + if render_window_type_str == 'Matplotlib': + if not is_chart: + return qt_matplotlib_render_window.CustomQtMatplotlibRenderWindow + else: + return qt_matplotlib_chart_render_window.CustomQtMatplotlibChartRenderWindow + elif render_window_type_str == 'VTK': + if not is_chart: + return qt_vtk_render_window.CustomQtVTKRenderWindow + else: + return qt_vtk_render_chart_window.CustomQtVTKChartRenderWindow + else: + if not is_chart: + return qt_vtk_render_window.CustomQtVTKRenderWindow + else: + return qt_vtk_render_chart_window.CustomQtVTKChartRenderWindow \ No newline at end of file diff --git a/view/image_visualization/vtk/__init__.py b/view/image_visualization/vtk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/vtk/qt_point_cloud_render_window.py b/view/image_visualization/vtk/qt_point_cloud_render_window.py new file mode 100644 index 0000000..aabee54 --- /dev/null +++ b/view/image_visualization/vtk/qt_point_cloud_render_window.py @@ -0,0 +1,177 @@ + + +import numpy as np +import vtk +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.image_visualization.vtk import qt_vtk_interactor +from view.image_visualization.vtk.rendering_methods import text_renderer + + +class PointCloudVtkQtRenderWindow: + def __init__(self, widget, model_view): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.image_renderer = vtk.vtkRenderer() + self.image_renderer.ResetCamera() + self.mask = None + if widget is not None: + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + interactor_style = vtk.vtkInteractorStyleRubberBand3D() + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame, interactor_style=interactor_style) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + self.render_window = self.vtk_widget.GetRenderWindow() + self.vtk_widget.set_active_renderer(self.image_renderer) + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + else: + self.render_window = vtk.vtkRenderWindow() + self.render_window.AddRenderer(self.image_renderer) + self.render_window.SetNumberOfLayers(3) + self.image_renderer.SetLayer(0) + self.widget = vtk.vtkOrientationMarkerWidget() + # SCALE ACTOR + renderWindowInteractor = self.render_window.GetInteractor() + axes = vtk.vtkAxesActor() + self.widget = vtk.vtkOrientationMarkerWidget() + self.widget.SetOrientationMarker(axes) + self.widget.SetInteractor(renderWindowInteractor) + self.widget.SetEnabled(1) + self.widget.InteractiveOn() + + def set_background_color(self, background_color): + self.render_window.GetRenderers().GetFirstRenderer().SetBackground(background_color[:3]) + + def _get_text_actors(self, image_number, scan_index=None): + text_actor = text_renderer.generate_text_actor(model_view=self.model_view, image_number=image_number, + scan_index=scan_index) + return text_actor + + def render(self, image_number, show_scale=True, show_points=True, z_scale=1.0, greyscale=False, + color_map_percentiles=None): + self.image_renderer.RemoveAllViewProps() + delaunay_mesh_actor, point_actor = self.generate_delaunay_actor(image_number=image_number, + show_points=show_points, z_scale=z_scale, + greyscale=greyscale, + color_map_percentiles=color_map_percentiles) + text_actor = self._get_text_actors(image_number=image_number) + self.image_renderer.AddActor(delaunay_mesh_actor) + self.image_renderer.AddActor(text_actor) + if point_actor is not None: + self.image_renderer.AddActor(point_actor) + if show_scale: + scale_actor = self.generate_scale_actor() + self.image_renderer.AddActor(scale_actor) + self.update() + + @staticmethod + def get_colors(): + colors = vtk.vtkUnsignedCharArray() + colors.SetNumberOfComponents(3) + colors.SetName('Colors') + return colors + + @staticmethod + def get_color_lookup_table(minz, maxz): + colorLookupTable = vtk.vtkLookupTable() + colorLookupTable.SetTableRange(minz, maxz) + colorLookupTable.UseAboveRangeColorOn() + colorLookupTable.UseBelowRangeColorOn() + colorLookupTable.Build() + return colorLookupTable + + def generate_delaunay_actor(self, image_number, show_points=True, z_scale=1.0, color_map_percentiles=None, + greyscale=False): + if color_map_percentiles is None: + color_map_percentiles = [0, 100] + data = self.model_view.get_1d_signal_by_number(image_number) + colors = vtk.vtkNamedColors() + data[:, 2] = data[:, 2] * z_scale + # Create a set of heights on a grid. + # This is often called a "terrain map". + points = vtk.vtkPoints() + for x, y, z, *_ in data: + points.InsertNextPoint(x, y, z) + + # Add the grid points to a polydata object + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + + delaunay = vtk.vtkDelaunay2D() + delaunay.SetInputData(polydata) + + # Visualize + meshMapper = vtk.vtkPolyDataMapper() + meshMapper.SetInputConnection(delaunay.GetOutputPort()) + + meshActor = vtk.vtkActor() + meshActor.SetMapper(meshMapper) + meshActor.GetProperty().SetColor(colors.GetColor3d('Banana')) + meshActor.GetProperty().EdgeVisibilityOn() + if show_points: + glyphFilter = vtk.vtkVertexGlyphFilter() + glyphFilter.SetInputData(polydata) + + pointMapper = vtk.vtkPolyDataMapper() + pointMapper.SetInputConnection(glyphFilter.GetOutputPort()) + + pointActor = vtk.vtkActor() + pointActor.GetProperty().SetColor(colors.GetColor3d('Tomato')) + pointActor.GetProperty().SetPointSize(5) + pointActor.SetMapper(pointMapper) + else: + pointActor = None + colorLookupTable = self.get_color_lookup_table(np.percentile(data[:, 2], color_map_percentiles[0]), + np.percentile(data[:, 2], color_map_percentiles[1])) + colors_points = self.get_colors() + # COLOR STUFF + for i in range(0, polydata.GetNumberOfPoints()): + p = 3 * [0.0] + polydata.GetPoint(i, p) + + dcolor = 3 * [0.0] + colorLookupTable.GetColor(p[2], dcolor); + # print( 'dcolor: {:<8.6} {:<8.6} {:<8.6}'.format(*dcolor)) + color = 3 * [255.0] + grey_value = 0.2989 * dcolor[0] + 0.5870 * dcolor[1] + 0.1140 * dcolor[2] + for j in range(0, 3): + if greyscale: + color[j] = int(color[j] * grey_value) + else: + color[j] = int(color[j] * dcolor[j]) + # print('color: {:<8} {:<8} {:<8}'.format(*color)) + + try: + colors_points.InsertNextTupleValue(color) + except AttributeError: + # For compatibility with new VTK generic data arrays. + colors_points.InsertNextTypedTuple(color) + polydata.GetPointData().SetScalars(colors_points) + return meshActor, pointActor + + def activate_click_signals(self): + self.left_click_position_signal = self.vtk_widget.left_click_position_signal + self.right_click_position_signal = self.vtk_widget.right_click_position_signal + + @staticmethod + def generate_scale_actor(): + scale_actor = vtk.vtkLegendScaleActor() + scale_actor.AllAxesOff() + return scale_actor + + def reset_render_window(self): + self.image_renderer.RemoveAllViewProps() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.image_renderer.ResetCamera() diff --git a/view/image_visualization/vtk/qt_vtk_interactor.py b/view/image_visualization/vtk/qt_vtk_interactor.py new file mode 100644 index 0000000..cdf8901 --- /dev/null +++ b/view/image_visualization/vtk/qt_vtk_interactor.py @@ -0,0 +1,84 @@ +import vtk +import vtk.qt +from PyQt5 import QtCore + +vtk.qt.QVTKRWIBase = "QGLWidget" +from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor + + +class CustomQtVtkRenderWindow(QVTKRenderWindowInteractor): + POINT_PICK_KEY = "e" + FEATURE_PICK_KEY = "q" + NEXT_IMAGE_KEY = "d" + PREVIOUS_IMAGE_KEY = "a" + FEATURE_PICK_MODE = "feature" + POINT_PICK_MODE = "point" + full_screen_signal = QtCore.pyqtSignal(bool) + right_click_position_signal = QtCore.pyqtSignal(list) + left_click_position_signal = QtCore.pyqtSignal(list) + pick_method_changed_signal = QtCore.pyqtSignal(str) + hover_signal = QtCore.pyqtSignal(list) + key_press_signal = QtCore.pyqtSignal(str) + next_image_signal = QtCore.pyqtSignal() + previous_image_signal = QtCore.pyqtSignal() + + def __init__(self, frame, interactor_style=None): + super().__init__(frame) + interactor_style = interactor_style or vtk.vtkInteractorStyleImage() + self.GetRenderWindow().GetInteractor().SetInteractorStyle(interactor_style) + self.pick_mode = None + self.AddObserver(vtk.vtkCommand.LeftButtonPressEvent, self.left_button_press_event) + self.AddObserver(vtk.vtkCommand.RightButtonPressEvent, self.right_button_press_event) + self.AddObserver(vtk.vtkCommand.KeyPressEvent, self.key_press_callback) + self.AddObserver(vtk.vtkCommand.MouseMoveEvent, self.mouse_hover_callback) + + def set_active_renderer(self, renderer): + self.GetInteractorStyle().SetDefaultRenderer(renderer) + + def key_press_callback(self, *args, **kwargs): + if self.GetKeySym() == "f": + self.full_screen_signal.emit(True) + elif self.GetKeySym() == self.FEATURE_PICK_KEY: + self.pick_mode = self.FEATURE_PICK_MODE + self.pick_method_changed_signal.emit(self.pick_mode) + elif self.GetKeySym() == self.POINT_PICK_KEY: + self.pick_mode = self.POINT_PICK_MODE + self.pick_method_changed_signal.emit(self.pick_mode) + elif self.GetKeySym() == self.NEXT_IMAGE_KEY: + self.next_image_signal.emit() + elif self.GetKeySym() == self.PREVIOUS_IMAGE_KEY: + self.previous_image_signal.emit() + self.key_press_signal.emit(self.GetKeySym()) + + def mouse_hover_callback(self, *args, **kwargs): + click_position = self.GetEventPosition() + picker = vtk.vtkPropPicker() + default_renderer = self.GetInteractorStyle().GetDefaultRenderer() + if default_renderer is None: + return + picker.Pick(click_position[0], click_position[1], 0, default_renderer) + position = picker.GetPickPosition() + if position == (0.0, 0.0, 0.0): + return + self.hover_signal.emit(list(position)) + + + def left_button_press_event(self, *args, **kwargs): + click_position = self.GetEventPosition() + picker = vtk.vtkPropPicker() + default_renderer = self.GetInteractorStyle().GetDefaultRenderer() + if default_renderer is None: + return + picker.Pick(click_position[0], click_position[1], 0, default_renderer) + position = picker.GetPickPosition() + if position == (0.0, 0.0, 0.0): + return + self.left_click_position_signal.emit(list(position)) + + + def right_button_press_event(self, *args, **kwargs): + click_position = self.GetEventPosition() + picker = vtk.vtkPropPicker() + picker.Pick(click_position[0], click_position[1], 0, self.GetRenderWindow().GetRenderers().GetFirstRenderer()) + position = picker.GetPickPosition() + self.right_click_position_signal.emit(list(position)) diff --git a/view/image_visualization/vtk/qt_vtk_render_chart_window.py b/view/image_visualization/vtk/qt_vtk_render_chart_window.py new file mode 100644 index 0000000..0f10727 --- /dev/null +++ b/view/image_visualization/vtk/qt_vtk_render_chart_window.py @@ -0,0 +1,83 @@ +import vtk +from PyQt5 import QtWidgets, QtCore + +from view.image_visualization.vtk import qt_vtk_interactor +from view.image_visualization.vtk.rendering_methods import vtk_chart_visualization + + +class CustomQtVTKChartRenderWindow(QtCore.QObject): + selected_points_signal = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view, fourier_transformed=False): + QtCore.QObject.__init__(self) + self.fourier_transformed = fourier_transformed + self.model_view = model_view + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + renderer = vtk.vtkRenderer() + self.render_window = self.vtk_widget.GetRenderWindow() + self.render_window.AddRenderer(renderer) + self.view = vtk.vtkContextView() + self.view.SetRenderWindow(self.render_window) + self.chart = vtk.vtkChartXY() + self.chart.SetActionToButton(vtk.vtkChartXY.SELECT, vtk.vtkContextMouseEvent.LEFT_BUTTON) + self.chart.AddObserver(vtk.vtkContextMouseEvent.LEFT_BUTTON, self.left_button_press_event) + self.view.GetScene().AddItem(self.chart) + self.render_window.SetMultiSamples(0) + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + self.selected_points_list = list() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.render_window.GetRenderers().GetFirstRenderer().ResetCamera() + self.render_window.Render() + + def render(self, scan_index, image_number, filter_type="1d", maximum_number_of_points=None): + if filter_type == "1d_full_signal": + full_1d_signal = True + else: + full_1d_signal = False + if full_1d_signal: + if self.fourier_transformed: + image = self.model_view.get_full_1d_signal_fourier_transformed(scan_index=scan_index) + else: + image = self.model_view.get_full_1d_signal(scan_index=scan_index) + else: + image = self.model_view.get_1d_signal_by_number(number=image_number, scan_index=None, + fourier_transformed=self.fourier_transformed) + frequency_sampling = self.model_view.get_freqeuncy_samlping(scan_index) + vtk_chart_visualization.visualize_image_as_chart(vtk_chart=self.chart, image=image, + logarithmic=self.fourier_transformed, + fourier_transformed=self.fourier_transformed, + frequency_sampling=frequency_sampling, + maximum_number_of_points=maximum_number_of_points) + self.update() + + def reset_render_window(self): + self.render_window.GetRenderers().GetFirstRenderer().RemoveAllViewProps() + + def left_button_press_event(self, *args, **kwargs): + self.selected_points_list = list() + if self.chart.GetPlot(0).GetSelection() is not None: + number_of_selected_points = self.chart.GetPlot(0).GetSelection().GetNumberOfTuples() + else: + return + for i in range(number_of_selected_points): + selected_point_id = int(self.chart.GetPlot(0).GetSelection().GetTuple1(i)) + x_axis_value = self.chart.GetPlot(0).GetInput().GetRow(selected_point_id).GetValue(0) + y_axis_value = self.chart.GetPlot(0).GetInput().GetRow(selected_point_id).GetValue(1) + self.selected_points_list.append([x_axis_value, y_axis_value]) + self.selected_points_signal.emit(self.selected_points_list) + diff --git a/view/image_visualization/vtk/qt_vtk_render_window.py b/view/image_visualization/vtk/qt_vtk_render_window.py new file mode 100644 index 0000000..4b129bd --- /dev/null +++ b/view/image_visualization/vtk/qt_vtk_render_window.py @@ -0,0 +1,211 @@ +import numpy as np +import vtk +from PyQt5 import QtWidgets +from skimage import segmentation + +from model_view import frontend_adapter +from view.image_visualization.vtk import qt_vtk_interactor +from view.image_visualization.vtk.rendering_methods import feature_visualization_vtk, text_renderer +from view.image_visualization.vtk.rendering_methods import vtk_single_image +from view.saasmi_visualization.vtk import saasmi_render_window + + +class CustomQtVTKRenderWindow: + def __init__(self, widget, model_view, fourier_transformed=False, handle_features: bool = False, + draw_features: bool = True): + self.handle_features = handle_features + if self.handle_features is True: + self.draw_features = True + else: + self.draw_features = draw_features + self.fourier_transformed = fourier_transformed + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.image_renderer = vtk.vtkRenderer() + self.feature_renderer = vtk.vtkRenderer() + self.rag_renderer = vtk.vtkRenderer() + image_camera = self.image_renderer.GetActiveCamera() # type: vtk.vtkCamera + # SET VTK (0,0) POINT TO TOP LEFT + image_camera.SetFocalPoint(0, 0, 0) + image_camera.SetPosition(0, 0, -1) + image_camera.SetViewUp(0, -1, 0) + self.feature_renderer.SetActiveCamera(image_camera) + self.rag_renderer.SetActiveCamera(image_camera) + self.image_renderer.ResetCamera() + self.mask = None + if widget is not None: + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + self.render_window = self.vtk_widget.GetRenderWindow() + self.vtk_widget.set_active_renderer(self.image_renderer) + if self.handle_features is True: + self.full_screen_signal = self.vtk_widget.full_screen_signal + self.right_click_position_signal = self.vtk_widget.right_click_position_signal + self.left_click_position_signal = self.vtk_widget.left_click_position_signal + self.pick_method_changed_signal = self.vtk_widget.pick_method_changed_signal + self.next_image_signal = self.vtk_widget.next_image_signal + self.hover_signal = self.vtk_widget.hover_signal + self.previous_image_signal = self.vtk_widget.previous_image_signal + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + else: + self.render_window = vtk.vtkRenderWindow() + self.render_window.AddRenderer(self.image_renderer) + self.render_window.AddRenderer(self.rag_renderer) + self.render_window.AddRenderer(self.feature_renderer) + self.render_window.SetNumberOfLayers(3) + self.image_renderer.SetLayer(0) + self.rag_renderer.SetLayer(1) + self.feature_renderer.SetLayer(2) + if self.draw_features is True or self.handle_features is True: + self.feature_visualization = feature_visualization_vtk.FeatureVisualization2D(model_view=self.model_view) + + def set_background_color(self, background_color): + self.render_window.GetRenderers().GetFirstRenderer().SetBackground(background_color[:3]) + + def _get_image_actor(self, image_number, scan_index=None, mask=None): + image = self.model_view.get_2d_image_by_number(image_number, scan_index, + fourier_transformed=self.fourier_transformed, + logarithmic=self.fourier_transformed) + nan_color = self.model_view.get_nan_color(scan_index) + if self.fourier_transformed is False: + contrast_settings = self.model_view.get_contrast_settings(scan_index) + else: + contrast_settings = None + step_size = self.model_view.get_step_size(scan_index) + image_actor = vtk_single_image.generate_2d_image_actor(image=image, + nan_color=nan_color, + contrast_settings=contrast_settings, step_size=step_size, + mask=mask) + return image_actor + + def _get_feature_actors(self, image_number, scan_index, feature_to_highlight=None): + feature_actors_balloon_text_tuple = self.feature_visualization.generate_feature_actors( + image_number=image_number, + scan_index=scan_index, feature_to_highlight=feature_to_highlight) + return feature_actors_balloon_text_tuple + + def _get_text_actors(self, image_number, scan_index): + text_actor = text_renderer.generate_text_actor(model_view=self.model_view, image_number=image_number, + scan_index=scan_index) + return text_actor + + def render(self, scan_index, image_number, mask=None, feature_to_highlight=None, show_rag=False, + show_features=True, show_feature_grid=False, show_scale=True): + self.image_renderer.RemoveAllViewProps() + self.feature_renderer.RemoveAllViewProps() + self.rag_renderer.RemoveAllViewProps() + image_actor = self._get_image_actor(image_number=image_number, scan_index=scan_index, mask=mask) + text_actor = self._get_text_actors(image_number=image_number, scan_index=scan_index) + if show_rag is True: + self.add_rag_to_renderer(scan_index, image_number) + if self.draw_features and show_features: + self.add_features_to_renderer(scan_index, image_number, feature_to_highlight) + if show_feature_grid: + self.render_feature_grid() + self.image_renderer.AddActor(image_actor) + self.image_renderer.AddActor(text_actor) + if show_scale: + scale_actor = self.generate_scale_actor() + self.image_renderer.AddActor(scale_actor) + self.update() + + def generate_poly_data_from_point_list(self, point_list): + vtk_points = vtk.vtkPoints() + for point in point_list: + vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_poly_data = vtk.vtkPolyData() + vtk_poly_data.SetPoints(vtk_points) + return vtk_poly_data + + def render_feature_grid(self): + point_list = self.model_view.get_feature_grid_list() + feature_grid_polydata = self.generate_poly_data_from_point_list(point_list) + step_size = self.model_view.get_step_size() + visualization_settings = self.model_view.get_feature_grid_visualization_settings() + polygon_source = vtk.vtkRegularPolygonSource() + polygon_source.SetNumberOfSides(50) + polygon_source.SetRadius(step_size[0] * visualization_settings["point_size"] / 2) + polygon_source.Update() + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(polygon_source.GetOutputPort()) + glyph_3d.SetInputData(feature_grid_polydata) + glyph_3d.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + color = visualization_settings["color"] + color = [c / 255 for c in color] + actor.GetProperty().SetColor(color[:3]) + actor.GetProperty().SetOpacity(color[3]) + actor.SetMapper(mapper) + self.feature_renderer.AddActor(actor) + + def activate_click_signals(self): + self.left_click_position_signal = self.vtk_widget.left_click_position_signal + self.right_click_position_signal = self.vtk_widget.right_click_position_signal + + @staticmethod + def generate_scale_actor(): + scale_actor = vtk.vtkLegendScaleActor() + scale_actor.AllAxesOff() + return scale_actor + + def add_features_to_renderer(self, scan_index, image_number, feature_to_highlight=None): + self.add_one_actor_for_feature(scan_index, image_number, feature_to_highlight) + + def add_one_actor_for_feature(self, scan_index, image_number, feature_to_highlight=None): + feature_class_actor_list = self.feature_visualization.generate_fast_rendering_actors( + image_number=image_number, + scan_index=scan_index, feature_to_highlight=feature_to_highlight) + highlight_actor = self.feature_visualization.generate_highlight_actor( + image_number=image_number, + scan_index=scan_index, feature_to_highlight=feature_to_highlight) + for actor in feature_class_actor_list: + self.feature_renderer.AddActor(actor) + if highlight_actor: + self.feature_renderer.AddActor(highlight_actor) + + def reset_render_window(self): + self.image_renderer.RemoveAllViewProps() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.image_renderer.ResetCamera() + + def add_rag_to_renderer(self, scan_index, image_number): + rag_actor = self._get_rag_actor(scan_index, image_number) + self.rag_renderer.AddActor(rag_actor) + + def _get_rag_actor(self, scan_index, image_number): + rag, labels = self.model_view.get_saasmi_rag_and_label(image_number=image_number, scan_index=scan_index) + degree_labels = saasmi_render_window.GraphVisualization.colorize_degrees(rag, labels) + image_label_overlay = saasmi_render_window.GraphVisualization.generate_color_image_by_labels(self.model_view, + degree_labels) + border_color = self.model_view.get_config_by_key_sub_key("saasmi", "border_color") + image_label_overlay = segmentation.mark_boundaries(image_label_overlay, labels, color=border_color) + image_label_overlay = image_label_overlay.astype(np.uint8) + image_label_overlay = np.fliplr(np.rot90(image_label_overlay, -1)) + vtk_image_data = saasmi_render_window.GraphVisualization.numpy_array_as_vtk_image_data(image_label_overlay) + step_size = self.model_view.get_step_size(scan_index) + vtk_image_data.SetSpacing(step_size[0], step_size[1], 1) + image_data_geometry_filter = vtk.vtkImageDataGeometryFilter() + image_data_geometry_filter.SetInputData(vtk_image_data) + image_data_geometry_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(image_data_geometry_filter.GetOutputPort()) + mapper.Update() + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetOpacity(0.5) + return actor diff --git a/view/image_visualization/vtk/rendering_methods/__init__.py b/view/image_visualization/vtk/rendering_methods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/image_visualization/vtk/rendering_methods/feature_visualization_vtk.py b/view/image_visualization/vtk/rendering_methods/feature_visualization_vtk.py new file mode 100644 index 0000000..cd1a562 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/feature_visualization_vtk.py @@ -0,0 +1,241 @@ +import colorsys + +import vtk +from vtk import vtkPolyLine + + +def get_feature_to_highlight(feature_to_highlight): + try: + feature_class_name = feature_to_highlight['feature_class_name'] + except(TypeError, KeyError): + feature_class_name = None + try: + feature_index = feature_to_highlight['feature_index'] + except(TypeError, KeyError): + feature_index = None + try: + point_index = feature_to_highlight['point_index'] + except(TypeError, KeyError): + point_index = None + return feature_class_name, feature_index, point_index + + +HIGHLIGHT_COLOR_MODI = ["None", "brightness", 'complementary', "fixed"] + + +class AbstractFeatureVisualization(): + def __init__(self, model_view): + self.model_view = model_view + + def get_color_and_highlight_color(self, color): + color = list(color) + if len(color) == 3: + color.append(255) + mode = self.model_view.get_config_by_key_sub_key("feature_settings", "highlight_method") + color = list(color) + if len(color) == 3: + color.append(255) + if mode == HIGHLIGHT_COLOR_MODI[0]: + return color, color + elif mode == HIGHLIGHT_COLOR_MODI[1]: + return self.get_brighter_highlight_color(color) + elif mode == HIGHLIGHT_COLOR_MODI[2]: + return self.get_complementary_highlight_color(color) + elif mode == HIGHLIGHT_COLOR_MODI[3]: + return self.get_fixed_highlight_color(color) + + def get_complementary_highlight_color(self, color): + """returns RGB components of complementary color""" + r, g, b, _a = color + hsv = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255) + highlight_color = list(colorsys.hsv_to_rgb((hsv[0] + 0.5) % 1, hsv[1], hsv[2])) + highlight_color = [c * 255 for c in highlight_color] + highlight_color.append(255) + return color, highlight_color + + def get_fixed_highlight_color(self, color): + highlight_color = self.model_view.get_config_by_key_sub_key("feature_settings", "highlight_color") + return color, highlight_color + + def get_brighter_highlight_color(self, color): + brightness = self.model_view.get_config_by_key_sub_key("feature_settings", "highlight_brightness") + r, g, b, _a = color + hls = colorsys.rgb_to_hls(r / 255, g / 255, b / 255) + if hls[1] > 1 - brightness: + highlight_color = color + color_hls = list(hls) + color_hls[1] = color_hls[1] - brightness + color = colorsys.hls_to_rgb(color_hls[0], color_hls[1], color_hls[2]) + color = [c * 255 for c in color] + color.append(255) + return color, highlight_color + else: + highlight_color = list(hls) + highlight_color[1] = highlight_color[1] + brightness + highlight_color = colorsys.hls_to_rgb(*highlight_color) + highlight_color = [c * 255 for c in highlight_color] + highlight_color.append(255) + return color, highlight_color + + +class FeatureVisualization2D(AbstractFeatureVisualization): + def __init__(self, model_view): + AbstractFeatureVisualization.__init__(self, model_view) + + def get_disk_source(self, radius, feature_class_name): + polygon_source = vtk.vtkDiskSource() + polygon_source.SetInnerRadius(radius) + thickness_percent = self.model_view.get_feature_class_visualization_by_item_name("feature_thickness_percent", + feature_class_name=feature_class_name) + polygon_source.SetOuterRadius(radius + radius * thickness_percent / 100) + polygon_source.SetCircumferentialResolution(50) + return polygon_source + + def get_circle_source(self, radius): + polygon_source = vtk.vtkRegularPolygonSource() + polygon_source.SetNumberOfSides(50) + polygon_source.SetRadius(radius) + polygon_source.Update() + return polygon_source + + def get_sphere_source(self, radius): + polygon_source = vtk.vtkSphereSource() + polygon_source.SetRadius(radius) + polygon_source.Update() + return polygon_source + + def get_feature_polygon_source(self, feature_point_representation, feature_class_name, radius): + if feature_point_representation == "Disk": + source = self.get_disk_source(radius, feature_class_name) + elif feature_point_representation == "Circle": + source = self.get_circle_source(radius) + else: + source = self.get_sphere_source(radius) + return source + + def generate_center_poly_data(self, scan_index, image_number, feature_class_name, feature_to_highlight): + highlight_class_name, highlight_feature_index, highlight_point_index = get_feature_to_highlight( + feature_to_highlight) + vtk_points = vtk.vtkPoints() + feature_dict = self.model_view.get_feature_dictionary(image_number=image_number, scan_index=scan_index) + features = feature_dict[feature_class_name] + cells = vtk.vtkCellArray() + for feature_index, points in features.items(): + vtk_poly_line = None + if len(points) <= 1: + for index, point in enumerate(points): + condition_feature_class_chosen = feature_class_name == highlight_class_name + condition_feature_index_chosen = feature_index == highlight_feature_index or highlight_feature_index is None + condition_feature_point_index_chosen = index == highlight_point_index or highlight_point_index is None + if not condition_feature_class_chosen or not condition_feature_index_chosen or not condition_feature_point_index_chosen: + vtk_points.InsertNextPoint(point[0], point[1], 0) + else: + vtk_poly_line = vtk.vtkPolyLine() + vtk_poly_line.GetPointIds().SetNumberOfIds(len(points)) + for index, point in enumerate(points): + vtk_point = vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_poly_line.GetPointIds().SetId(index, vtk_point) + if vtk_poly_line: + cells.InsertNextCell(vtk_poly_line) + vtk_poly_data = vtk.vtkPolyData() + vtk_poly_data.SetLines(cells) + vtk_poly_data.SetPoints(vtk_points) + return vtk_poly_data + + def generate_actor_for_feature_class(self, feature_class_name, scan_index, image_number, feature_to_highlight): + center_poly_data = self.generate_center_poly_data(scan_index, image_number, feature_class_name, + feature_to_highlight) + step_size = self.model_view.get_step_size(scan_index) + step_size = step_size[0] + feature_radius = self.model_view.get_feature_class_visualization_by_item_name("feature_point_size", + feature_class_name=feature_class_name) * step_size + + feature_point_representation = self.model_view.get_feature_class_visualization_by_item_name("feature_geometry", + feature_class_name=feature_class_name) + polygon_source = self.get_feature_polygon_source(feature_point_representation, + feature_class_name=feature_class_name, radius=feature_radius) + # Create Glyphes + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(polygon_source.GetOutputPort()) + glyph_3d.SetInputData(center_poly_data) + glyph_3d.Update() + + # Visualize + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + color = self.model_view.get_feature_class_color_by_name(feature_class_name=feature_class_name, + scan_index=scan_index) + + actor_glyph = vtk.vtkActor() + actor_glyph.GetProperty().SetColor(color[0] / 255, color[1] / 255, color[2] / 255) + actor_glyph.SetMapper(mapper) + + # Create Lines + + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(center_poly_data) + color = self.model_view.get_feature_class_color_by_name(feature_class_name=feature_class_name, + scan_index=scan_index) + + actor_lines = vtk.vtkActor() + actor_lines.GetProperty().SetColor(color[0] / 255, color[1] / 255, color[2] / 255) + actor_lines.GetProperty().SetLineWidth(feature_radius/8) + actor_lines.SetMapper(mapper) + + return [actor_glyph, actor_lines] + + def generate_fast_rendering_actors(self, scan_index, image_number, feature_to_highlight): + actor_list = [] + feature_dict = self.model_view.get_feature_dictionary(image_number=image_number, scan_index=scan_index) + for feature_class_name in feature_dict.keys(): + actor_list.extend(self.generate_actor_for_feature_class(feature_class_name, scan_index, image_number, + feature_to_highlight)) + return actor_list + + def generate_highlight_actor(self, image_number, scan_index, feature_to_highlight): + if not feature_to_highlight: + return None + else: + feature_class_name = feature_to_highlight["feature_class_name"] + center_poly_data = self.generate_center_highlighted_poly_data(scan_index, image_number, feature_to_highlight) + step_size = self.model_view.get_step_size(scan_index) + step_size = step_size[0] + feature_radius = self.model_view.get_feature_class_visualization_by_item_name("feature_point_size", + feature_class_name=feature_class_name) * step_size + feature_point_representation = self.model_view.get_feature_class_visualization_by_item_name("feature_geometry", + feature_class_name=feature_class_name) + polygon_source = self.get_feature_polygon_source(feature_point_representation, feature_class_name, + radius=feature_radius) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(polygon_source.GetOutputPort()) + glyph_3d.SetInputData(center_poly_data) + glyph_3d.Update() + + # Visualize + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + color = self.model_view.get_feature_class_color_by_name(feature_class_name=feature_class_name, + scan_index=scan_index) + if color is not None: + color, highlight_color = self.get_color_and_highlight_color(color) + actor.GetProperty().SetColor(highlight_color[0] / 255, highlight_color[1] / 255, highlight_color[2] / 255) + return actor + + def generate_center_highlighted_poly_data(self, scan_index, image_number, feature_to_highlight): + vtk_points = vtk.vtkPoints() + highlight_class_name, highlight_feature_index, highlight_point_index = get_feature_to_highlight( + feature_to_highlight) + feature_dict = self.model_view.get_feature_dictionary(image_number=image_number, scan_index=scan_index) + for feature_class, features in feature_dict.items(): + for feature_index, points in features.items(): + for index, point in enumerate(points): + condition_feature_class_chosen = feature_class == highlight_class_name + condition_feature_index_chosen = feature_index == highlight_feature_index or highlight_feature_index is None + condition_feature_point_index_chosen = index == highlight_point_index or highlight_point_index is None + if condition_feature_class_chosen and condition_feature_index_chosen and condition_feature_point_index_chosen: + vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_poly_data = vtk.vtkPolyData() + vtk_poly_data.SetPoints(vtk_points) + return vtk_poly_data diff --git a/view/image_visualization/vtk/rendering_methods/text_renderer.py b/view/image_visualization/vtk/rendering_methods/text_renderer.py new file mode 100644 index 0000000..7f24625 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/text_renderer.py @@ -0,0 +1,31 @@ +import vtk + +from model import config + + +def generate_text_actor(model_view, scan_index, image_number): + time_stamp_text = model_view.get_time_stamp_text(image_number, scan_index) + image_class_name = model_view.get_scan_direction_by_image_number(image_number) + text_actor = vtk.vtkTextActor() + txtprop = text_actor.GetTextProperty() + txtprop.SetFontFamilyToArial() + txtprop.SetFontSize(config.config["time_stamps"]["font_size"]) + r, g, b = config.config["time_stamps"]["color"]["red"], config.config["time_stamps"]["color"]["green"], \ + config.config["time_stamps"]["color"]["blue"] + txtprop.SetColor(r / 255, g / 255, b / 255) + show_time_stamps_bool = model_view.get_config_by_key_sub_key("time_stamps", "show_time_stamps") + show_scan_direction_bool = model_view.get_config_by_key_sub_key("time_stamps", "show_scan_direction") + pre_text = model_view.get_config_by_key_sub_key("time_stamps", "pre_text") + scan_direction_exists = image_class_name != "None" + if show_time_stamps_bool and show_scan_direction_bool and scan_direction_exists: + input_text = "Image Class: {} \n{} {}".format(image_class_name, pre_text, time_stamp_text) + elif not show_scan_direction_bool and show_time_stamps_bool: + input_text = "{} {}".format(pre_text, time_stamp_text) + elif not show_time_stamps_bool and show_scan_direction_bool: + input_text = "Image Class: {}".format(image_class_name) + else: + input_text = "" + text_actor.SetInput(input_text) + text_actor.SetDisplayPosition(config.config["time_stamps"]["position"][0], + config.config["time_stamps"]["position"][1]) + return text_actor diff --git a/view/image_visualization/vtk/rendering_methods/visualize_as_image.py b/view/image_visualization/vtk/rendering_methods/visualize_as_image.py new file mode 100644 index 0000000..dcc45b8 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/visualize_as_image.py @@ -0,0 +1,70 @@ +import logging +import pickle + +import vtk + +from view.image_visualization.vtk.qt_vtk_render_window import CustomQtVTKRenderWindow + +log = logging.getLogger(__name__) +supported_file_formats = ['.png', '.jpg'] + +custom_render_window = None + + +def export_as_image(file_path, model_view, background_color, image_number, + resolution, file_format, show_rag=False, show_features=False, show_scale=True): + # Hack so that the render window is not deleted by the garbage collector + global custom_render_window + if file_format == "pickle": + image = model_view.get_2d_image_by_number(image_number) + pickle.dump(image, open(file_path + ".pickle", "wb")) + return + + if custom_render_window is None: + custom_render_window = CustomQtVTKRenderWindow(widget=None, model_view=model_view) + vtk_render_window = custom_render_window.render_window + vtk_render_window.SetSize(resolution[0], resolution[1]) + vtk_render_window.SetOffScreenRendering(1) + vtk_render_window.SetUseOffScreenBuffers(1) + vtk_window_to_image_filter = vtk.vtkWindowToImageFilter() + vtk_window_to_image_filter.SetInput(vtk_render_window) + vtk_window_to_image_filter.SetInputBufferTypeToRGB() + vtk_window_to_image_filter.ReadFrontBufferOff() + vtk_window_to_image_filter.Update() + if file_format == "png": + writer = vtk.vtkPNGWriter() + else: + file_format = "jpg" + writer = vtk.vtkJPEGWriter() + writer.SetInputConnection(vtk_window_to_image_filter.GetOutputPort()) + writer.SetFileName(file_path + "." + file_format) + custom_render_window.render(scan_index=None, image_number=image_number, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + background_color = [color / 255 for color in background_color] + custom_render_window.set_background_color(background_color) + custom_render_window.reset_render_camera() + vtk_window_to_image_filter.Modified() + vtk_render_window.Start() + writer.Write() + + +def visualize_preview_image(model_view, background_color, image_number, + resolution, widget=None, show_rag=False, show_features=False, show_scale=True): + # Hack so that the render window is not deleted by the garbage collector + global custom_render_window + custom_render_window = CustomQtVTKRenderWindow(widget=widget, model_view=model_view) + vtk_render_window = custom_render_window.render_window + vtk_render_window.SetSize(resolution[0], resolution[1]) + custom_render_window.render(scan_index=None, image_number=image_number, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + background_color = [color / 255 for color in background_color] + custom_render_window.set_background_color(background_color) + custom_render_window.reset_render_camera() + vtk_render_window.Render() + vtk_render_window.Start() + + +def delete_render_window(): + global custom_render_window + custom_render_window.render_window.Finalize() + custom_render_window = None diff --git a/view/image_visualization/vtk/rendering_methods/visualize_as_video.py b/view/image_visualization/vtk/rendering_methods/visualize_as_video.py new file mode 100644 index 0000000..ab74773 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/visualize_as_video.py @@ -0,0 +1,107 @@ +import logging +import os + +import imageio +import vtk + +from view.image_visualization.vtk.qt_vtk_render_window import CustomQtVTKRenderWindow + +log = logging.getLogger(__name__) +supported_file_formats = ['.ogv', '.avi', '.mp4'] + + +def export_as_gif(file_path, model_view, background_color, start_frame, end_frame, frames_per_second, + resolution, file_format, show_rag=False, show_features=False, show_scale=True): + custom_render_window = CustomQtVTKRenderWindow(widget=None, model_view=model_view) + vtk_render_window = custom_render_window.render_window + vtk_render_window.SetSize(resolution[0], resolution[1]) + vtk_render_window.SetOffScreenRendering(1) + vtk_render_window.SetUseOffScreenBuffers(1) + vtk_window_to_image_filter = vtk.vtkWindowToImageFilter() + + vtk_window_to_image_filter.SetInput(vtk_render_window) + vtk_window_to_image_filter.SetInputBufferTypeToRGB() + vtk_window_to_image_filter.ReadFrontBufferOff() + vtk_window_to_image_filter.Update() + vtk_render_window.Start() + custom_render_window.render(scan_index=None, image_number=start_frame, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + background_color = [color / 255 for color in background_color] + custom_render_window.set_background_color(background_color) + custom_render_window.reset_render_camera() + if start_frame == end_frame: + end_frame = model_view.get_maximum_image_index() + writer = vtk.vtkJPEGWriter() + os.mkdir(path=file_path) + image_list = [] + for current_image_number in range(start_frame, end_frame + 1): + custom_render_window.render(scan_index=None, image_number=current_image_number, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + + vtk_window_to_image_filter.Modified() + writer.SetFileName(file_path + "/{}.jpeg".format(current_image_number)) + writer.SetInputConnection(vtk_window_to_image_filter.GetOutputPort()) + writer.Write() + image = imageio.imread(file_path + "/{}.jpeg".format(current_image_number)) + image_list.append(image) + imageio.mimsave(file_path + ".gif", image_list, duration=(1 / frames_per_second)) + return file_path + file_format + + +def export_as_video(file_path, model_view, background_color, start_frame, end_frame, frames_per_second, + resolution, file_format, show_rag=False, show_features=False, show_scale=True): + if file_format == ".gif": + return export_as_gif(file_path, model_view, background_color, start_frame, end_frame, frames_per_second, + resolution, file_format, show_rag, show_features, show_scale) + custom_render_window = CustomQtVTKRenderWindow(widget=None, model_view=model_view) + vtk_render_window = custom_render_window.render_window + vtk_render_window.SetSize(resolution[0], resolution[1]) + vtk_render_window.SetOffScreenRendering(1) + vtk_render_window.SetUseOffScreenBuffers(1) + vtk_window_to_image_filter = vtk.vtkWindowToImageFilter() + + vtk_window_to_image_filter.SetInput(vtk_render_window) + vtk_window_to_image_filter.SetInputBufferTypeToRGB() + vtk_window_to_image_filter.ReadFrontBufferOff() + vtk_window_to_image_filter.Update() + movie_writer = vtk.vtkOggTheoraWriter() + movie_writer.SetRate(frames_per_second) + + movie_writer.SetInputConnection(vtk_window_to_image_filter.GetOutputPort()) + movie_writer.SetQuality(2) + if file_format != ".ogv": + tmp_file_name = 'tmp.ovg' + else: + tmp_file_name = file_path + file_format + movie_writer.SetFileName(tmp_file_name) + movie_writer.Start() + vtk_render_window.Start() + custom_render_window.render(scan_index=None, image_number=start_frame, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + background_color = [color / 255 for color in background_color] + custom_render_window.set_background_color(background_color) + custom_render_window.reset_render_camera() + time_stamps = model_view.get_time_stamps() + if start_frame == end_frame: + end_frame = model_view.get_maximum_image_index() + for current_image_number in range(start_frame, end_frame + 1): + custom_render_window.render(scan_index=None, image_number=current_image_number, show_rag=show_rag, + show_features=show_features, show_scale=show_scale) + + vtk_window_to_image_filter.Modified() + movie_writer.Write() + movie_writer.End() + if file_format != '.ogv': + try: + os.path.realpath(file_path) + except: + log.warning("The given file path is not valid") + return + if file_format not in supported_file_formats: + log.warning("Fileformat is not supported") + return + os.system( + 'ffmpeg -y -i {tmp_file} -c:v libx264 -preset slow -profile:v high -level:v 4.0 -pix_fmt yuv420p -crf 22 -codec:a aac {output_file}'.format( + tmp_file=tmp_file_name, output_file=file_path + file_format)) + os.remove(tmp_file_name) + return file_path + file_format diff --git a/view/image_visualization/vtk/rendering_methods/vtk_chart_visualization.py b/view/image_visualization/vtk/rendering_methods/vtk_chart_visualization.py new file mode 100644 index 0000000..f768d21 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/vtk_chart_visualization.py @@ -0,0 +1,70 @@ +import numpy as np +import vtk +from vtk.util import numpy_support + + +def visualize_image_as_chart(vtk_chart: vtk.vtkChartXY, image, logarithmic, fourier_transformed, frequency_sampling, + maximum_number_of_points=None): + image = image.copy() + chart = vtk_chart + chart.ClearPlots() + table = vtk.vtkTable() + y_axis = image[:, 2] + if not fourier_transformed: + time_delta = 1 / frequency_sampling + numpy_array_x_axis = np.arange(image.shape[0]) * time_delta + if maximum_number_of_points is not None: + numpy_array_x_axis, y_axis = reduce_number_of_points_normal(x_axis=numpy_array_x_axis, y_axis=y_axis, + maximum_number_of_points=maximum_number_of_points) + vtk_array_x_axis = numpy_support.numpy_to_vtk(numpy_array_x_axis) + vtk_array_x_axis.SetName("Time Axis") + else: + frequency_delta = frequency_sampling / image.shape[0] + frequency_delta = round(frequency_delta, 3) + numpy_array_x_axis = np.arange(image.shape[0]) * frequency_delta + numpy_array_x_axis -= (image.shape[0] / 2 * frequency_delta) + if maximum_number_of_points is not None: + numpy_array_x_axis, y_axis = reduce_number_of_points_fft(x_axis=numpy_array_x_axis, y_axis=y_axis, + maximum_number_of_points=maximum_number_of_points) + vtk_array_x_axis = numpy_support.numpy_to_vtk(numpy_array_x_axis) + vtk_array_x_axis.SetName("Frequency Axis") + + if len(image.shape) > 1: + array_points = numpy_support.numpy_to_vtk(y_axis) + else: + array_points = numpy_support.numpy_to_vtk(image) + array_points.SetName("data points") + table.SetNumberOfRows(image.shape[0]) + table.GetRowData().AddArray(vtk_array_x_axis) + table.GetRowData().AddArray(array_points) + points = chart.AddPlot(vtk.vtkChart.LINE) + points.SetInputData(table, 0, 1) + points.SetColor(0, 0, 0, 255) + + +def reduce_number_of_points_fft(x_axis, y_axis, maximum_number_of_points): + if y_axis.shape[0] <= maximum_number_of_points or maximum_number_of_points == 0: + return x_axis, y_axis + tmp_args = np.argpartition(-y_axis, maximum_number_of_points) + result_args = tmp_args[:maximum_number_of_points] + result_args.sort() + x_axis_clean = x_axis[result_args] + y_axis_clean = y_axis[result_args] + return x_axis_clean, y_axis_clean + + +def average(arr, n): + """stolen from https://stackoverflow.com/questions/10847660/subsampling-averaging-over-a-numpy-array/47066522""" + end = n * int(len(arr) / n) + return np.mean(arr[:end].reshape(-1, n), 1) + + +def reduce_number_of_points_normal(x_axis, y_axis, maximum_number_of_points): + if y_axis.shape[0] <= maximum_number_of_points or maximum_number_of_points == 0: + return x_axis, y_axis + bin_size = int(x_axis.shape[0] / maximum_number_of_points) + y_axis_clean = average(y_axis, bin_size) + number_of_bins = y_axis_clean.shape[0] + x_axis_clean = x_axis[:number_of_bins] + x_axis_clean *= bin_size + return x_axis_clean, y_axis_clean diff --git a/view/image_visualization/vtk/rendering_methods/vtk_single_image.py b/view/image_visualization/vtk/rendering_methods/vtk_single_image.py new file mode 100644 index 0000000..b39ca94 --- /dev/null +++ b/view/image_visualization/vtk/rendering_methods/vtk_single_image.py @@ -0,0 +1,59 @@ +import numpy as np +import vtk +from vtk.util import numpy_support + + +def generate_2d_image_actor(image, contrast_settings=None, nan_color=None, step_size=None, mask=None): + if step_size is None: + step_size = [1, 1] + if contrast_settings is None: + contrast_settings = ['normal'] + if nan_color is None: + nan_color = (0, 0, 0, 0) + min_z, max_z = np.nanmin(image), np.nanmax(image) + if mask is not None: + image = image * mask + vtk_array = numpy_support.numpy_to_vtk( + num_array=image.ravel(), + deep=True, + array_type=vtk.VTK_DOUBLE) + vtk_image_data = vtk.vtkImageData() + dimensions = image.shape + vtk_image_data.SetDimensions(dimensions[1], dimensions[0], 1) + vtk_image_data.AllocateScalars(vtk.VTK_DOUBLE, 1) + vtk_image_data.GetPointData().SetScalars(vtk_array) + # Setting up a greyscale lookup table + grey_scale_color_table = vtk.vtkLookupTable() + grey_scale_color_table.SetSaturationRange(0, 0) + if contrast_settings[0] == "percentile": + if np.isnan(image).all(): + percentile = (0, 0) + else: + percentile = np.percentile(image[~np.isnan(image)], (contrast_settings[1], contrast_settings[2])) + grey_scale_color_table.SetRange(percentile[0], percentile[1]) + elif contrast_settings[0] == "absolute": + grey_scale_color_table.SetRange(contrast_settings[1], contrast_settings[2]) + else: + grey_scale_color_table.SetRange(min_z, max_z) + grey_scale_color_table.SetBelowRangeColor(0, 1, 1, 1) + grey_scale_color_table.SetAboveRangeColor(1, 0, 1, 1) + grey_scale_color_table.SetUseAboveRangeColor(0) + grey_scale_color_table.SetUseBelowRangeColor(0) + grey_scale_color_table.SetHueRange(0, 0) + grey_scale_color_table.SetValueRange(0, 1) + grey_scale_color_table.SetNanColor(nan_color) + vtk_image_data.SetSpacing(step_size[0], step_size[1], 1) + image_data_geometry_filter = vtk.vtkImageDataGeometryFilter() + image_data_geometry_filter.SetInputData(vtk_image_data) + image_data_geometry_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetLookupTable(grey_scale_color_table) + mapper.SetUseLookupTableScalarRange(1) + mapper.SetInputConnection(image_data_geometry_filter.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + grey_scale_color_table.Build() + mapper.Update() + actor = vtk.vtkActor() + actor.SetMapper(mapper) + return actor diff --git a/view/import_feature_dialog.py b/view/import_feature_dialog.py new file mode 100644 index 0000000..ef4e7f3 --- /dev/null +++ b/view/import_feature_dialog.py @@ -0,0 +1,49 @@ +import logging +import os + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_import_features + +log = logging.getLogger(__name__) + + +class ImportFeatureDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, ): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_import_features.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.setup_ui_fields() + self.ui_dialog.pushButton_browse_file_system.clicked.connect(self.handle_browse_filesystem) + + def setup_ui_fields(self): + file_path = self.model_view.get_config_by_key_sub_key("import_feature", "file_path") + feature_ratio = self.model_view.get_config_by_key_sub_key("import_feature", "feature_ratio") + if file_path: + self.ui_dialog.lineEdit_file_path.setText(file_path) + self.ui_dialog.doubleSpinBox_x_ratio.setValue(feature_ratio[0]) + self.ui_dialog.doubleSpinBox_y_ratio.setValue(feature_ratio[1]) + + def handle_browse_filesystem(self): + path = self.ui_dialog.lineEdit_file_path.text() + if path != "" and os.path.exists(path): + if os.path.isdir(path): + directory = path + else: + directory = os.path.dirname(path) + else: + directory = os.getcwd() + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open feature file", directory, options=options, + filter="Feature Pickle File (*.pkl)") + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def accept(self): + file_path = self.ui_dialog.lineEdit_file_path.text() + feature_ratio = [self.ui_dialog.doubleSpinBox_x_ratio.value(), self.ui_dialog.doubleSpinBox_y_ratio.value()] + self.model_view.set_config_by_key_sub_key("import_feature", "file_path", file_path) + self.model_view.set_config_by_key_sub_key("import_feature", "feature_ratio", feature_ratio) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/import_gwy_dialog.py b/view/import_gwy_dialog.py new file mode 100644 index 0000000..55a2c0c --- /dev/null +++ b/view/import_gwy_dialog.py @@ -0,0 +1,65 @@ +import logging +import os + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_import_gwy + +log = logging.getLogger(__name__) + + +class ImportGWYDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, file_path=None): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_import_gwy.Ui_DialogImportGWY() + self.ui_dialog.setupUi(self) + self.ui_dialog.pushButton_browse_filesystem.clicked.connect(self.handle_browse_filesystem) + self.ui_dialog.lineEdit_file_path.textChanged.connect(self.handle_line_edit_change) + if file_path is None: + file_path = self.get_gwy_config_by_key('file_path') + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def handle_browse_filesystem(self): + path = self.ui_dialog.lineEdit_file_path.text() + if path != "" and os.path.exists(path): + if os.path.isdir(path): + directory = path + else: + directory = os.path.dirname(path) + else: + directory = os.getcwd() + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open gwy file", directory, options=options, + filter="Gwyiddion File (*.gwy)") + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def handle_line_edit_change(self): + file_path = self.ui_dialog.lineEdit_file_path.text() + if file_path[-4:].lower() == ".gwy" and os.path.exists( + file_path): + ui_inputs = self.model_view.get_gwy_inputs(file_path=file_path) + self.ui_dialog.comboBox_gwy_channel.clear() + for channel in ui_inputs: + self.ui_dialog.comboBox_gwy_channel.addItem(channel) + self.ui_dialog.comboBox_gwy_channel.setEnabled(True) + try: + self.ui_dialog.comboBox_gwy_channel.setCurrentText(self.get_gwy_config_by_key('channel')) + except: + pass + else: + self.ui_dialog.comboBox_gwy_channel.setEnabled(False) + + def accept(self): + self.set_gwy_config_by_key('channel', self.ui_dialog.comboBox_gwy_channel.currentText()) + self.set_gwy_config_by_key('file_path', self.ui_dialog.lineEdit_file_path.text()) + self.model_view.load_file(file_path=self.get_gwy_config_by_key('file_path')) + self.done(QtWidgets.QDialog.Accepted) + + def get_gwy_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('import_gwy', key) + + def set_gwy_config_by_key(self, key, value): + self.model_view.set_config_by_key_sub_key('import_gwy', key, value) diff --git a/view/import_h5_dialog.py b/view/import_h5_dialog.py new file mode 100644 index 0000000..ff46b00 --- /dev/null +++ b/view/import_h5_dialog.py @@ -0,0 +1,114 @@ +import logging +import os + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_import_h5 + +log = logging.getLogger(__name__) + + +class ImportH5Dialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, file_path=None): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_import_h5.Ui_DialogImportH5() + self.ui_dialog.setupUi(self) + self.set_up_dialog(file_path) + + def set_up_dialog(self, file_path): + self.ui_dialog.spinBox_number_of_bins.setValue(self.get_h5_config_by_key('number_of_bins')) + available_to_grid_methods = self.model_view.get_availible_to_grid_methods() + self.ui_dialog.comboBox_to_grid_method.addItems(available_to_grid_methods) + self.ui_dialog.comboBox_to_grid_method.setCurrentText( + self.get_h5_config_by_key("to_grid_method")) + if self.get_h5_config_by_key('visualyse_window') == "square": + self.ui_dialog.radioButton_square_scan.setChecked(True) + else: + self.ui_dialog.radioButton_full_scan.setChecked(True) + use_first_image_xy_values = self.get_h5_config_by_key("use_first_image_xy_values") + use_ideal_xy_values = self.get_h5_config_by_key("use_ideal_xy_values") + self.ui_dialog.checkBox_use_first_image_xy_values.setChecked(use_first_image_xy_values) + self.ui_dialog.checkBox_use_ideal_xy_values.setChecked(use_ideal_xy_values) + self.ui_dialog.checkBox_cut_zoom.setChecked(self.get_h5_config_by_key('cut_zoom')) + if self.get_h5_config_by_key('file_path') and os.path.exists(self.get_h5_config_by_key('file_path')): + if file_path is None: + file_path = self.get_h5_config_by_key('file_path') + self.ui_dialog.lineEdit_file_path.setText(file_path) + self.ui_dialog.pushButton_browse_filesystem.clicked.connect(self.handle_browse_filesystem) + import_images = self.get_h5_config_by_key('import_images') + self.ui_dialog.spinBox_import_image_start.setValue(import_images[0]) + self.ui_dialog.spinBox_import_image_end.setValue(import_images[1]) + self.ui_dialog.checkBox_remove_offset.setChecked(self.get_h5_config_by_key('remove_offset')) + self.scan_directions_dict = {"In and Out combined": self.ui_dialog.checkBox_scan_direction_combined, + "In and Out seperated": self.ui_dialog.checkBox_scan_direction_seperated, + "In": self.ui_dialog.checkBox_scan_direction_in, + "Out": self.ui_dialog.checkBox_scan_direction_out} + scan_directions = self.get_h5_config_by_key("scan_directions") + z_value_4th_column = self.get_h5_config_by_key("use_4ths_column") + self.ui_dialog.checkBox_use_4th_as_z.setChecked(z_value_4th_column) + for scan_direction in scan_directions: + self.scan_directions_dict[scan_direction].setChecked(True) + + def handle_browse_filesystem(self): + path = self.ui_dialog.lineEdit_file_path.text() + if path != "" and os.path.exists(path): + if os.path.isdir(path): + directory = path + else: + directory = os.path.dirname(path) + else: + directory = os.getcwd() + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open h5 file", directory, options=options, + filter="H5 Files (*.h5)") + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def get_h5_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('import_h5', key) + + def set_h5_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('import_h5', key, value) + + def get_false_inputs(self): + scan_directions_checked = [self.ui_dialog.checkBox_scan_direction_seperated.isChecked(), + self.ui_dialog.checkBox_scan_direction_out.isChecked(), + self.ui_dialog.checkBox_scan_direction_in.isChecked(), + self.ui_dialog.checkBox_scan_direction_combined.isChecked(), ] + if not any(scan_directions_checked): + return "At least one scan direction has to be checked" + + def accept(self): + false_inputs = self.get_false_inputs() + if self.ui_dialog.lineEdit_file_path.text() == "": + false_inputs = "No File Path was given" + if false_inputs is not None: + QtWidgets.QMessageBox.about(self, "Info", "The following input is incorrect:\n {}".format(false_inputs)) + return + + self.set_h5_config_by_key('number_of_bins', self.ui_dialog.spinBox_number_of_bins.value()) + self.set_h5_config_by_key('cut_zoom', self.ui_dialog.checkBox_cut_zoom.isChecked()) + self.set_h5_config_by_key('file_path', self.ui_dialog.lineEdit_file_path.text()) + self.set_h5_config_by_key('import_images', + [self.ui_dialog.spinBox_import_image_start.value(), + self.ui_dialog.spinBox_import_image_end.value()]) + self.set_h5_config_by_key('to_grid_method', self.ui_dialog.comboBox_to_grid_method.currentText()) + self.set_h5_config_by_key("use_first_image_xy_values", + self.ui_dialog.checkBox_use_first_image_xy_values.isChecked()) + self.set_h5_config_by_key("use_ideal_xy_values", + self.ui_dialog.checkBox_use_ideal_xy_values.isChecked()) + self.set_h5_config_by_key("use_4ths_column", self.ui_dialog.checkBox_use_4th_as_z.isChecked()) + if self.ui_dialog.radioButton_square_scan.isChecked(): + self.set_h5_config_by_key('visualyse_window', "square") + else: + self.set_h5_config_by_key('visualyse_window', None) + scan_direction_list = [] + for key, check_box in self.scan_directions_dict.items(): + if check_box.isChecked(): + scan_direction_list.append(key) + self.set_h5_config_by_key("scan_directions", scan_direction_list) + self.set_h5_config_by_key('remove_offset', self.ui_dialog.checkBox_remove_offset.isChecked()) + self.model_view.load_file(self.get_h5_config_by_key('file_path')) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/import_npy_dialog.py b/view/import_npy_dialog.py new file mode 100644 index 0000000..a96df9f --- /dev/null +++ b/view/import_npy_dialog.py @@ -0,0 +1,67 @@ +import logging +import os + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_import_npy + +log = logging.getLogger(__name__) + + +class ImportNpyDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None, file_path=None): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_import_npy.Ui_DialogImportNpy() + self.ui_dialog.setupUi(self) + self.set_up_dialog(file_path) + + def set_up_dialog(self, file_path): + self.ui_dialog.spinBox_number_of_bins.setValue(self.get_npy_config_by_key('number_of_bins')) + available_to_grid_methods = self.model_view.get_availible_to_grid_methods() + self.ui_dialog.comboBox_to_grid_method.addItems(available_to_grid_methods) + self.ui_dialog.comboBox_to_grid_method.setCurrentText( + self.get_npy_config_by_key("to_grid_method")) + if self.get_npy_config_by_key('file_path') and os.path.exists(self.get_npy_config_by_key('file_path')): + if type(file_path) == list: + file_path = file_path[0] + if file_path is None: + file_path = self.get_npy_config_by_key('file_path') + self.ui_dialog.lineEdit_file_path.setText(file_path) + self.ui_dialog.pushButton_browse_filesystem.clicked.connect(self.handle_browse_filesystem) + + def handle_browse_filesystem(self): + path = self.ui_dialog.lineEdit_file_path.text() + if path != "" and os.path.exists(path): + if os.path.isdir(path): + directory = path + else: + directory = os.path.dirname(path) + else: + directory = os.getcwd() + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open npy file", directory, options=options, + filter="Numpy Files (*.npy)") + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def get_npy_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('import_npy', key) + + def set_npy_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('import_npy', key, value) + + def accept(self): + false_inputs = None + if self.ui_dialog.lineEdit_file_path.text() == "": + false_inputs = "No File Path was given" + if false_inputs is not None: + QtWidgets.QMessageBox.about(self, "Info", "The following input is incorrect:\n {}".format(false_inputs)) + return + + self.set_npy_config_by_key('number_of_bins', self.ui_dialog.spinBox_number_of_bins.value()) + self.set_npy_config_by_key('file_path', self.ui_dialog.lineEdit_file_path.text()) + self.set_npy_config_by_key('to_grid_method', self.ui_dialog.comboBox_to_grid_method.currentText()) + self.model_view.load_file(self.get_npy_config_by_key('file_path')) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/jump_visualization_dialog.py b/view/jump_visualization_dialog.py new file mode 100644 index 0000000..ffe8c8c --- /dev/null +++ b/view/jump_visualization_dialog.py @@ -0,0 +1,258 @@ + + +import logging +import parser + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.feature_jump_visualization.matplotlib import jump_visualization +from view.ui import ui_dialog_jump_visualization + +log = logging.getLogger(__name__) + + +class JumpVisualizationDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent) + log.info("Open Jump Visualization Dialog") + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_jump_visualization.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.initilize_selected_feature_dict(selected_feature_classes=[], selected_feature_dict={}) + self.setWindowFlags( + QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowCloseButtonHint) + + self.update_feature_class_list_widget() + self.generate_jump_visualization_render_window() + self.ui_dialog.pushButton_feature_class_check_all.clicked.connect(self.handle_feature_class_check_all) + self.ui_dialog.pushButton_feature_class_uncheck_all.clicked.connect(self.handle_feature_class_uncheck_all) + self.ui_dialog.pushButton_feature_class_invert.clicked.connect(self.handle_feature_class_invert) + self.ui_dialog.pushButton_feature_list_check_all.clicked.connect(self.handle_feature_list_check_all) + self.ui_dialog.pushButton_feature_list_uncheck_all.clicked.connect(self.handle_feature_list_uncheck_all) + self.ui_dialog.pushButton_feature_list_invert.clicked.connect(self.handle_feature_list_invert) + self.ui_dialog.listWidget_feature_class.currentItemChanged.connect(self.handle_change_feature_class) + self.ui_dialog.listWidget_feature.currentItemChanged.connect(self.handle_change_feature_index) + self.ui_dialog.lineEdit_jump_threshold.returnPressed.connect(self.handle_threshold_changed) + self.update_jump_list_widget() + + def get_feature_list_by_class_index(self, class_index): + try: + return self.selected_feature_dict[class_index] + except KeyError: + self.selected_feature_dict[class_index] = [] + return self.selected_feature_dict[class_index] + + def handle_change_feature_class(self): + self.update_feature_list_widget() + self.update_jump_list_widget() + + def handle_change_feature_index(self): + self.update_jump_visualization_render_window() + + def handle_analyse_window_changed(self): + self.update_jump_visualization_render_window() + + def change_analyse_render_window(self): + self.generate_jump_visualization_render_window() + self.update_jump_visualization_render_window() + + def handle_left_click(self, position_data): + selected_feature_dict = self.get_back_end_selected_feature_dict() + selected_feature_classes = self.selected_feature_classes + nearest_jump_information = self.model_view.get_nearest_jump_information( + selected_feature_classes=selected_feature_classes, + selected_feature_dict=selected_feature_dict, + position=position_data) + self.ui_dialog.textBrowser_jump_information.clear() + jump_information_str = "" + for key, value in nearest_jump_information.items(): + jump_information_str += "{}: {} \n \n".format(key, value) + self.ui_dialog.textBrowser_jump_information.setText(jump_information_str) + + def generate_jump_visualization_render_window(self): + self.analyse_widget = self.ui_dialog.widget_analyse_window + self.render_window = jump_visualization.JumpVisualization(widget=self.ui_dialog.widget_analyse_window, + model_view=self.model_view) + self.render_window.left_click_position.connect(self.handle_left_click) + self.update_jump_visualization_render_window() + + def get_thresold(self): + threshold_str = self.ui_dialog.lineEdit_jump_threshold.text() + if threshold_str != "": + try: + threshold = eval(parser.expr(threshold_str).compile()) + except SyntaxError as e: + log.info(e.text) + threshold = 0 + else: + threshold = 0 + return threshold + + def update_jump_visualization_render_window(self): + threshold = self.get_thresold() + selected_feature_dict = self.get_back_end_selected_feature_dict() + self.render_window.render(selected_feature_classes=self.selected_feature_classes, + selected_feature_dict=selected_feature_dict, threshold=threshold) + + def handle_threshold_changed(self): + self.update_jump_visualization_render_window() + self.update_jump_list_widget() + + def initilize_selected_feature_dict(self, selected_feature_classes=None, selected_feature_dict=None): + feature_class_list = self.model_view.get_feature_classes_list() + if selected_feature_classes is None: + self.selected_feature_classes = feature_class_list + else: + self.selected_feature_classes = selected_feature_classes + self.selected_feature_dict = {} + if selected_feature_dict is None: + for feature_class_index, feature_class_name in enumerate(feature_class_list): + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=feature_class_index) + self.selected_feature_dict[feature_class_name] = feature_list + for feature_class_name in feature_class_list: + if feature_class_name not in self.selected_feature_dict: + self.selected_feature_dict[feature_class_name] = [] + + def update_feature_class_list_widget(self): + feature_class_list = self.model_view.get_feature_classes_list() + self.ui_dialog.listWidget_feature_class.clear() + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature_class, + item_list=feature_class_list, + selected_item_list=self.selected_feature_classes, + check_box_call_back_function=self.get_selected_feature_classes) + self.ui_dialog.listWidget_feature_class.setCurrentRow(0) + self.update_feature_list_widget() + + def update_feature_list_widget(self): + current_feature_class_index = self.ui_dialog.listWidget_feature_class.currentRow() + self.ui_dialog.listWidget_feature.clear() + if current_feature_class_index >= 0: + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=current_feature_class_index) + current_feature_class_name = self.get_current_feature_class_name() + selected_item_list = self.get_feature_list_by_class_index(current_feature_class_name) + self.generate_feature_list_with_checkboxes(list_widget=self.ui_dialog.listWidget_feature, + item_list=feature_list, selected_item_list=selected_item_list, + check_box_call_back_function=self.set_selected_features_in_feature_dict) + self.ui_dialog.listWidget_feature.setCurrentRow(0) + + def get_current_feature_class_name(self): + list_widget = self.ui_dialog.listWidget_feature_class + current_index = list_widget.currentRow() + list_widget_item = list_widget.item(current_index) + label = list_widget.itemWidget(list_widget_item).children()[1] + return label.text() + + def get_back_end_selected_feature_dict(self): + selected_feature_dict = {} + for class_name, feature_list in self.selected_feature_dict.items(): + selected_feature_dict[class_name] = [int(feature) for feature in feature_list] + return selected_feature_dict + + def generate_feature_list_with_checkboxes(self, list_widget, item_list, selected_item_list, + check_box_call_back_function=None): + for item in item_list: + # Create widget + # Add widget to QListWidget funList + layout = QtWidgets.QHBoxLayout() + label = QtWidgets.QLabel(item) + check_box = QtWidgets.QCheckBox() + if check_box_call_back_function is not None: + check_box.clicked.connect(check_box_call_back_function) + if item in selected_item_list: + check_box.setChecked(True) + layout.addWidget(label) + layout.addWidget(check_box) + layout.addStretch() + list_widget_item = QtWidgets.QListWidgetItem() + widget = QtWidgets.QWidget() + layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + widget.setLayout(layout) + list_widget_item.setSizeHint(widget.sizeHint()) + list_widget.addItem(list_widget_item) + list_widget.setItemWidget(list_widget_item, widget) + + def get_selected_items_by_list_widget(self, list_widget): + item_list = [] + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + label = list_widget.itemWidget(list_widget_item).children()[1] + if check_box.isChecked(): + item_list.append(label.text()) + return item_list + + def update_ui(self): + self.update_jump_visualization_render_window() + self.update_jump_list_widget() + + def set_selected_features_in_feature_dict(self): + feature_class_name = self.get_current_feature_class_name() + selected_features = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature) + self.selected_feature_dict[feature_class_name] = selected_features + self.update_jump_list_widget() + self.update_jump_visualization_render_window() + + def get_selected_feature_classes(self): + self.selected_feature_classes = self.get_selected_items_by_list_widget(self.ui_dialog.listWidget_feature_class) + self.update_jump_list_widget() + self.update_jump_visualization_render_window() + + def handle_feature_class_check_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_check_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_uncheck_all(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_class_invert(self): + list_widget = self.ui_dialog.listWidget_feature_class + self.list_widget_invert(list_widget) + self.get_selected_feature_classes() + self.update_ui() + + def handle_feature_list_check_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_check_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_uncheck_all(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_uncheck_all(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def handle_feature_list_invert(self): + list_widget = self.ui_dialog.listWidget_feature + self.list_widget_invert(list_widget) + self.set_selected_features_in_feature_dict() + self.update_ui() + + def list_widget_check_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(True) + + def list_widget_uncheck_all(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(False) + + def list_widget_invert(self, list_widget): + for i in range(list_widget.count()): + list_widget_item = list_widget.item(i) + check_box = list_widget.itemWidget(list_widget_item).children()[2] + check_box.setChecked(not check_box.isChecked()) + + def update_jump_list_widget(self): + pass diff --git a/view/line_profile_dialog.py b/view/line_profile_dialog.py new file mode 100644 index 0000000..ccc1cb9 --- /dev/null +++ b/view/line_profile_dialog.py @@ -0,0 +1,373 @@ + + +import csv +import math +import parser + +import numpy as np +import scipy.optimize +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.line_profile_render_windows.vtk import render_window_image, render_window_line_profile +from view.ui import ui_dialog_line_profile + +unit_prefix_list = ["1", "m", "µ", "n", "p"] + + +class LineProfileDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.reset_line_list() + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint| QtCore.Qt.WindowCloseButtonHint) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_line_profile.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.starting_step_size = self.model_view.get_step_size() + self.image_widget = self.ui_dialog.widget_render_window_image + self.image_render_window = render_window_image.LineProfileImageRenderWindow( + widget=self.image_widget, + model_view=self.model_view) + self.line_profile_widget = self.ui_dialog.widget_render_window_profile + self.line_profile_render_window = render_window_line_profile.LineProfileProfileRenderWindow( + widget=self.line_profile_widget, model_view=self.model_view) + self.image_render_window.left_click_position_signal.connect(self.handle_left_click_in_image_window) + self.image_render_window.right_click_position_signal.connect(self.handle_right_click_in_image) + self.image_render_window.key_press_signal.connect(self.handle_key_press) + self.connect_ui_elements() + self.set_up_combo_boxes() + self.set_up_line_speeds() + self.update_windows() + self.reset_camera() + + def connect_ui_elements(self): + self.ui_dialog.comboBox_y_axis_prefix.currentIndexChanged.connect(self.update_profile_window) + self.ui_dialog.comboBox_x_axis_prefix.currentIndexChanged.connect(self.update_profile_window) + self.ui_dialog.radioButton_remove_offset_line_profile.toggled.connect(self.update_profile_window) + self.ui_dialog.radioButton_remove_offset_none.toggled.connect(self.update_profile_window) + self.ui_dialog.radioButton_remove_offset_whole_image.toggled.connect(self.update_profile_window) + self.ui_dialog.pushButton_add_line.clicked.connect(self.handle_add_line) + self.ui_dialog.pushButton_remove_line.clicked.connect(self.handle_remove_line) + self.ui_dialog.listWidget_lines.currentItemChanged.connect(self.handle_line_changed) + self.ui_dialog.pushButton_export_line_profile.clicked.connect(self.handle_export_line_profile) + self.ui_dialog.pushButton_calculate_new_step_size.clicked.connect(self.calculate_new_step_size) + + def set_up_line_speeds(self): + step_size = self.model_view.get_step_size() + self.ui_dialog.lineEdit_line_movement_speed.setText("{:4g}".format(step_size[0])) + self.ui_dialog.lineEdit_line_rotation_speed.setText("1") + + def set_up_combo_boxes(self): + self.ui_dialog.comboBox_x_axis_prefix.addItems(unit_prefix_list) + self.ui_dialog.comboBox_x_axis_prefix.setCurrentText("n") + self.ui_dialog.comboBox_y_axis_prefix.addItems(unit_prefix_list) + self.ui_dialog.comboBox_y_axis_prefix.setCurrentText("n") + + def update_windows(self): + self.update_image_window() + self.update_profile_window() + + def get_remove_offset_method(self): + if self.ui_dialog.radioButton_remove_offset_whole_image.isChecked(): + remove_offset_method = "whole_image" + elif self.ui_dialog.radioButton_remove_offset_line_profile.isChecked(): + remove_offset_method = "line_profile_range" + else: + remove_offset_method = None + return remove_offset_method + + def get_hight_profile_scale(self): + pass + + def update_image_window(self): + self.image_render_window.render(image_number=self.image_number, line_list=self.line_list) + + def get_line_profile_settings(self): + remove_offset_method = self.get_remove_offset_method() + height_profile_scale = self.get_hight_profile_scale() + x_axis_prefix = self.ui_dialog.comboBox_x_axis_prefix.currentText() + y_axis_prefix = self.ui_dialog.comboBox_y_axis_prefix.currentText() + line_profile_settings = {"remove_offset_method": remove_offset_method, + "height_profile_scale": height_profile_scale, + "x_axis_unit_prefix": x_axis_prefix, + "y_axis_unit_prefix": y_axis_prefix, } + return line_profile_settings + + def update_profile_window(self): + line_profile_settings = self.get_line_profile_settings() + self.line_profile_render_window.render(image_number=self.image_number, + line_profile_settings=line_profile_settings, line=self.chosen_line) + + def reset_camera(self): + self.image_render_window.reset_camera() + + def handle_left_click_in_image_window(self, click_position): + if self.chosen_line is None: + return + self.chosen_line[0] = click_position + self.update_windows() + self.update_list_widget() + + def handle_key_press(self, key): + key_handle_dict = { + "Up": self.handle_up_key, + "Down": self.handle_down_key, + "Left": self.handle_left_key, + "Right": self.handle_right_key, + "Insert": self.handle_insert_key, + "Delete": self.handle_delete_key + } + try: + key_handle_dict[key]() + except KeyError: + pass + self.update_windows() + + def handle_up_key(self): + speed = self.get_line_edit_values(self.ui_dialog.lineEdit_line_movement_speed) + self.chosen_line[0][1] -= speed + self.chosen_line[1][1] -= speed + + def handle_down_key(self): + speed = self.get_line_edit_values(self.ui_dialog.lineEdit_line_movement_speed) + self.chosen_line[0][1] += speed + self.chosen_line[1][1] += speed + + def handle_left_key(self): + speed = self.get_line_edit_values(self.ui_dialog.lineEdit_line_movement_speed) + self.chosen_line[0][0] -= speed + self.chosen_line[1][0] -= speed + + def handle_right_key(self): + speed = self.get_line_edit_values(self.ui_dialog.lineEdit_line_movement_speed) + self.chosen_line[0][0] += speed + self.chosen_line[1][0] += speed + + def handle_insert_key(self): + rotation_speed_degrees = self.get_line_edit_values(self.ui_dialog.lineEdit_line_rotation_speed) + self.chosen_line = self.rotate_line_around_middle_point(self.chosen_line, rotation_speed_degrees) + + def handle_delete_key(self): + rotation_speed_degrees = self.get_line_edit_values(self.ui_dialog.lineEdit_line_rotation_speed) + self.chosen_line = self.rotate_line_around_middle_point(self.chosen_line, -rotation_speed_degrees) + + def rotate_line_around_middle_point(self, line, degrees): + origin = self.get_line_middle_point(self.chosen_line) + start_point = self.rotate_point(line[0], origin, degrees) + end_point = self.rotate_point(line[1], origin, degrees) + return [start_point, end_point] + + def get_line_middle_point(self, line): + x_middle = (line[1][0] - line[0][0]) / 2 + line[0][0] + y_middle = (line[1][1] - line[0][1]) / 2 + line[0][1] + return [x_middle, y_middle] + + def rotate_point(self, point, origin, degrees): + radians = np.deg2rad(degrees) + x, y, z = point + offset_x, offset_y = origin + adjusted_x = (x - offset_x) + adjusted_y = (y - offset_y) + cos_rad = np.cos(radians) + sin_rad = np.sin(radians) + qx = offset_x + cos_rad * adjusted_x + sin_rad * adjusted_y + qy = offset_y + -sin_rad * adjusted_x + cos_rad * adjusted_y + return [qx, qy, z] + + def handle_right_click_in_image(self, click_position): + if self.chosen_line is None: + return + self.chosen_line[1] = click_position + self.update_windows() + self.update_list_widget() + + def get_line_edit_values(self, line_edit, default_value=None): + try: + value = eval(parser.expr(line_edit.text()).compile()) + return value + except SyntaxError: + return default_value + + def update_list_widget(self): + current_index = self.ui_dialog.listWidget_lines.currentRow() + self.ui_dialog.listWidget_lines.clear() + for line in self.line_list: + self.add_line_widget_to_widget_list(line, self.ui_dialog.listWidget_lines) + + if current_index >= self.ui_dialog.listWidget_lines.count(): + current_index = self.ui_dialog.listWidget_lines.count() - 1 + self.ui_dialog.listWidget_lines.setCurrentRow(current_index) + + def handle_add_line(self): + self.line_list.append([[0, 0, 0], [0, 0, 0]]) + self.update_list_widget() + last_index = self.ui_dialog.listWidget_lines.count() + self.ui_dialog.listWidget_lines.setCurrentRow(last_index - 1) + self.chosen_line = self.line_list[-1] + + def add_line_widget_to_widget_list(self, line, list_widget): + line_list_widget = LineListWidgetItem(line) + list_widget_item = QtWidgets.QListWidgetItem() + list_widget_item.setSizeHint(line_list_widget.sizeHint()) + list_widget.addItem(list_widget_item) + list_widget.setItemWidget(list_widget_item, line_list_widget) + + def handle_remove_line(self): + if len(self.line_list) == 0: + return + current_index = self.ui_dialog.listWidget_lines.currentRow() + self.line_list.pop(current_index) + self.update_list_widget() + if len(self.line_list) == 0: + self.chosen_line = None + elif current_index >= len(self.line_list): + self.chosen_line = self.line_list[-1] + else: + self.chosen_line = self.line_list[current_index] + self.update_windows() + + def handle_line_changed(self): + current_index = self.ui_dialog.listWidget_lines.currentRow() + if len(self.line_list) > 0: + self.chosen_line = self.line_list[current_index] + self.update_profile_window() + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) + + def reject(self) -> None: + self.model_view.set_step_size(*self.starting_step_size) + self.done(QtWidgets.QDialog.Rejected) + + def get_line_length_list(self): + line_length_list = [] + for i in range(self.ui_dialog.listWidget_lines.count()): + list_widget_item = self.ui_dialog.listWidget_lines.item(i) + line = self.line_list[i] + length_widget = self.ui_dialog.listWidget_lines.itemWidget(list_widget_item).children()[5] + length_str = length_widget.text() + length = eval(parser.expr(length_str).compile()) + line_length_list.append([line, length]) + return line_length_list + + def calculate_new_step_size(self): + x_s_scale, y_s_scale = self.get_step_size_scale() + step_size = self.model_view.get_step_size() + new_step_size = [step_size[0] * x_s_scale, step_size[1] * y_s_scale] + step_size_dialog = QtWidgets.QMessageBox + ret = step_size_dialog.question(self, '', + "The the calculated step size is [{:.4g}, {:.4g}], the old one was [{:.4g},{:.4g}], are you sure you want to change it?".format( + *new_step_size, *step_size), + step_size_dialog.Yes | step_size_dialog.No) + if ret == step_size_dialog.Yes: + self.model_view.set_step_size(*new_step_size) + self.reset_line_list() + self.update_list_widget() + self.update_windows() + self.reset_camera() + + def reset_line_list(self): + self.line_list = [] + self.chosen_line = None + + def get_step_size_scale(self): + line_length_list = self.get_line_length_list() + x_y_sqaure_list = [] + length_list = [] + for line, length in line_length_list: + len_x = abs(line[0][0] - line[1][0]) + len_y = abs(line[0][1] - line[1][1]) + x_y_sqaure_list.append([len_x ** 2, len_y ** 2]) + length_list.append(length ** 2) + coefficent_matrix = np.array(x_y_sqaure_list) + length_matrix = np.array(length_list) + linear_solution_vector = scipy.optimize.nnls(coefficent_matrix, length_matrix) + x_s_scale_squared, y_s_scale_squared = linear_solution_vector[0] + x_s_scale = math.sqrt(x_s_scale_squared) + y_s_scale = math.sqrt(y_s_scale_squared) + return x_s_scale, y_s_scale + + def handle_export_line_profile(self): + dialog = LineProfileExportDialog(self) + lines = [] + if dialog.exec_(): + file_path = dialog.line_edit_file_path.text() + image_number = self.image_number + line_profile_settings = self.get_line_profile_settings() + for line in self.line_list: + line_points, distance_between_points = self.line_profile_render_window.get_line_information( + image_number=image_number, line=line, line_profile_settings=line_profile_settings) + y_values = line_points + x_values = np.arange(len(line_points)) * distance_between_points + lines.append(zip(x_values, y_values)) + self.write_to_csv_file(file_path, lines) + + def write_to_csv_file(self, file_path, lines): + try: + file_path, extension = file_path.split(".") + file_path += ".csv" + except: + file_path += ".csv" + + with open(file_path, 'w') as csv_file: + writer = csv.writer(csv_file) + for line_id, line in enumerate(lines): + writer.writerow(["Line Number {}".format(line_id + 1)]) + for x, y in line: + writer.writerow([x, y]) + + +class LineListWidgetItem(QtWidgets.QWidget): + def pretty_format_line(self, line): + line_str = "{:.4g}, {:.4g} \n {:.4g}, {:.4g} ".format(line[0][0], line[0][1], line[1][0], line[1][1]) + return line_str + + def get_x_len_str(self, line): + x_len = abs(line[1][0] - line[0][0]) + x_len_str = "{:4g}".format(x_len) + return x_len_str + + def get_y_len_str(self, line): + y_len = abs(line[1][1] - line[0][1]) + y_len_str = "{:4g}".format(y_len) + return y_len_str + + def __init__(self, line): + super().__init__() + layout = QtWidgets.QGridLayout() + position_label = QtWidgets.QLabel(text="Line") + len_label = QtWidgets.QLabel( + text="len x: {} \n len y: {}".format(self.get_x_len_str(line), self.get_y_len_str(line))) + line_str = self.pretty_format_line(line) + self.line_label = QtWidgets.QLabel(text=line_str) + length_label = QtWidgets.QLabel(text="Length") + line_length = math.sqrt((line[0][1] - line[1][1]) ** 2 + (line[0][0] - line[1][0]) ** 2) + self.user_length = QtWidgets.QLineEdit() + self.user_length.setText("{:.6g}".format(line_length)) + layout.addWidget(position_label, 0, 0) + layout.addWidget(len_label, 0, 2) + layout.addWidget(self.line_label, 0, 1) + layout.addWidget(length_label, 1, 0) + layout.addWidget(self.user_length, 1, 1) + self.setLayout(layout) + + +class LineProfileExportDialog(QtWidgets.QDialog): + def __init__(self, parent): + super().__init__(parent=parent) + layout = QtWidgets.QGridLayout() + self.label_file_path = QtWidgets.QLabel(text="File Name") + self.line_edit_file_path = QtWidgets.QLineEdit() + self.button_box = QtWidgets.QDialogButtonBox(self) + self.button_box.setOrientation(QtCore.Qt.Horizontal) + self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.reject) + layout.addWidget(self.label_file_path, 0, 0) + layout.addWidget(self.line_edit_file_path, 0, 1) + layout.addWidget(self.button_box, 1, 0) + self.setLayout(layout) + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/line_profile_render_windows/__init__.py b/view/line_profile_render_windows/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/line_profile_render_windows/__init__.py @@ -0,0 +1 @@ + diff --git a/view/line_profile_render_windows/vtk/__init__.py b/view/line_profile_render_windows/vtk/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/line_profile_render_windows/vtk/__init__.py @@ -0,0 +1 @@ + diff --git a/view/line_profile_render_windows/vtk/render_window_image.py b/view/line_profile_render_windows/vtk/render_window_image.py new file mode 100644 index 0000000..cebb58a --- /dev/null +++ b/view/line_profile_render_windows/vtk/render_window_image.py @@ -0,0 +1,105 @@ + + +import numpy as np +import vtk +from PyQt5 import QtWidgets +from vtk.util import numpy_support + +from model_view import frontend_adapter +from view.image_visualization.vtk import qt_vtk_interactor +from view.image_visualization.vtk.rendering_methods import vtk_single_image + + +class LineProfileImageRenderWindow: + def __init__(self, widget, model_view): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.image_renderer = vtk.vtkRenderer() + self.line_renderer = vtk.vtkRenderer() + image_camera = self.image_renderer.GetActiveCamera() # type: vtk.vtkCamera + # SET VTK (0,0) POINT TO TOP LEFT + image_camera.SetFocalPoint(0, 0, 0) + image_camera.SetPosition(0, 0, -1) + image_camera.SetViewUp(0, -1, 0) + self.line_renderer.SetActiveCamera(image_camera) + self.image_renderer.ResetCamera() + if widget is not None: + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + self.render_window = self.vtk_widget.GetRenderWindow() + self.vtk_widget.set_active_renderer(self.image_renderer) + self.right_click_position_signal = self.vtk_widget.right_click_position_signal + self.left_click_position_signal = self.vtk_widget.left_click_position_signal + self.key_press_signal = self.vtk_widget.key_press_signal + self.pick_method_changed_signal = self.vtk_widget.pick_method_changed_signal + self.next_image_signal = self.vtk_widget.next_image_signal + self.previous_image_signal = self.vtk_widget.previous_image_signal + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + else: + self.render_window = vtk.vtkRenderWindow() + self.render_window.Start() + self.step_size = self.model_view.get_step_size() + self.render_window.AddRenderer(self.image_renderer) + self.render_window.AddRenderer(self.line_renderer) + self.render_window.SetNumberOfLayers(2) + self.image_renderer.SetLayer(0) + self.line_renderer.SetLayer(1) + + def set_background_color(self, background_color): + self.render_window.GetRenderers().GetFirstRenderer().SetBackground(background_color[:3]) + + def render(self, image_number, scan_index=None, line_list=None, contrast_settings=None): + self.image_renderer.RemoveAllViewProps() + self.line_renderer.RemoveAllViewProps() + contrast_settings = contrast_settings or self.model_view.get_contrast_settings() + image_actor = self._get_image_actor(image_number=image_number, scan_index=scan_index, + contrast_settings=contrast_settings) + self.image_renderer.AddActor(image_actor) + for line in line_list: + line_actor = self._get_line_actor(start_point=line[0], end_point=line[1]) + self.line_renderer.AddActor(line_actor) + self.update() + + def update(self): + self.render_window.Render() + + def _get_image_actor(self, image_number, scan_index, contrast_settings=None): + image = self.model_view.get_2d_image_by_number(image_number, scan_index) + nan_color = self.model_view.get_nan_color(scan_index) + step_size = self.model_view.get_step_size(scan_index) + image_actor = vtk_single_image.generate_2d_image_actor(image=image, + nan_color=nan_color, + contrast_settings=contrast_settings, step_size=step_size) + return image_actor + + def reset_camera(self): + self.image_renderer.ResetCamera() + + def _get_line_actor(self, start_point, end_point): + line_points = vtk.vtkPoints() + line_points.InsertNextPoint(start_point) + line_points.InsertNextPoint(end_point) + line_poly_data = vtk.vtkPolyData() + line_poly_data.SetPoints(line_points) + line = vtk.vtkLine() + line.GetPointIds().SetId(0, 0) + line.GetPointIds().SetId(1, 1) + lines = vtk.vtkCellArray() + lines.InsertNextCell(line) + line_poly_data.SetLines(lines) + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(line_poly_data) + actor = vtk.vtkActor() + actor.GetProperty().SetColor(0, 0, 1) + actor.SetMapper(mapper) + return actor + diff --git a/view/line_profile_render_windows/vtk/render_window_line_profile.py b/view/line_profile_render_windows/vtk/render_window_line_profile.py new file mode 100644 index 0000000..e86a1ca --- /dev/null +++ b/view/line_profile_render_windows/vtk/render_window_line_profile.py @@ -0,0 +1,131 @@ + + +import math + +import numpy as np +import skimage.measure +import vtk +from PyQt5 import QtWidgets, QtCore +from vtk.util import numpy_support + +from view.image_visualization.vtk import qt_vtk_interactor + +unit_prefix_dict = {"1": 1, "m": 1e3, "µ": 1e6, "n": 1e9, "p": 1e12} + + +class LineProfileProfileRenderWindow(QtCore.QObject): + selected_points_signal = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + QtCore.QObject.__init__(self) + self.model_view = model_view + self.frame = QtWidgets.QFrame() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vtk_widget = qt_vtk_interactor.CustomQtVtkRenderWindow(self.frame) + self.vl.addWidget(self.vtk_widget) + widget.setLayout(self.vl) + renderer = vtk.vtkRenderer() + self.render_window = self.vtk_widget.GetRenderWindow() + self.render_window.AddRenderer(renderer) + self.view = vtk.vtkContextView() + self.view.SetRenderWindow(self.render_window) + self.chart = vtk.vtkChartXY() + self.chart.SetActionToButton(vtk.vtkChartXY.SELECT, vtk.vtkContextMouseEvent.LEFT_BUTTON) + self.chart.AddObserver(vtk.vtkContextMouseEvent.LEFT_BUTTON, self.left_button_press_event) + self.view.GetScene().AddItem(self.chart) + self.render_window.SetMultiSamples(0) + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + self.selected_points_list = list() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.render_window.GetRenderers().GetFirstRenderer().ResetCamera() + self.render_window.Render() + + def render(self, image_number, line, line_profile_settings, scan_index=None, ): + self.chart.ClearPlots() + if line is None: + return + + self.render_line_profile(image_number, line, line_profile_settings, scan_index) + self.update() + + def reset_render_window(self): + self.render_window.GetRenderers().GetFirstRenderer().RemoveAllViewProps() + + def left_button_press_event(self, *args, **kwargs): + self.selected_points_list = list() + if self.chart.GetPlot(0).GetSelection() is not None: + number_of_selected_points = self.chart.GetPlot(0).GetSelection().GetNumberOfTuples() + else: + return + for i in range(number_of_selected_points): + selected_point_id = int(self.chart.GetPlot(0).GetSelection().GetTuple1(i)) + x_axis_value = self.chart.GetPlot(0).GetInput().GetRow(selected_point_id).GetValue(0) + y_axis_value = self.chart.GetPlot(0).GetInput().GetRow(selected_point_id).GetValue(1) + self.selected_points_list.append([x_axis_value, y_axis_value]) + self.selected_points_signal.emit(self.selected_points_list) + + def render_line_profile(self, image_number, line, line_profile_settings, scan_index=None): + line_points, distance_between_points = self.get_line_information(image_number, line, line_profile_settings) + if len(line_points) > 0: + self.visualize_line_plot(line_points=line_points, distance_between_points=distance_between_points, + x_axis_unit_prefix=line_profile_settings["x_axis_unit_prefix"], + y_axis_unit_prefix=line_profile_settings["y_axis_unit_prefix"]) + + def get_line_information(self, image_number, line, line_profile_settings): + image = self.model_view.get_2d_image_by_number(image_number) + step_size = self.model_view.get_step_size() + line_scaled = [] + for point in line: + line_scaled.append([point[1] / step_size[1], point[0] / step_size[0]]) + # extract values on line from r1, c1 to r2, c2 + src_x, src_y = line_scaled[0] + dest_x, dest_y = line_scaled[1] + len_x = abs(src_x - dest_x) + len_y = abs(src_y - dest_y) + if len_x == 0 and len_y == 0: + return [], 0 + image = np.nan_to_num(image) + zvalues = skimage.measure.profile_line(image=image, src=line_scaled[0], dst=line_scaled[1], order=3) + line_points = self.handle_remove_offset(image, zvalues, line_profile_settings["remove_offset_method"]) + num_points = len(line_points) + line_length = math.sqrt((len_x * step_size[0]) ** 2 + (len_y * step_size[1]) ** 2) + distance_between_points = line_length / num_points + return line_points, distance_between_points + + def visualize_line_plot(self, line_points, distance_between_points, x_axis_unit_prefix, y_axis_unit_prefix): + self.chart.ClearPlots() + table = vtk.vtkTable() + y_axis = line_points + y_axis_in_nm = y_axis * unit_prefix_dict[y_axis_unit_prefix] + numpy_array_x_axis = np.arange(len(line_points)) * ( + distance_between_points * unit_prefix_dict[x_axis_unit_prefix]) + self.chart.GetAxis(0).SetTitle("Height profile in [{}]".format(y_axis_unit_prefix)) + self.chart.GetAxis(1).SetTitle("Distance in [{}]".format(x_axis_unit_prefix)) + vtk_array_x_axis = numpy_support.numpy_to_vtk(numpy_array_x_axis) + vtk_array_x_axis.SetName("Frequency Axis") + array_points = numpy_support.numpy_to_vtk(y_axis_in_nm) + array_points.SetName("data points") + table.SetNumberOfRows(len(y_axis_in_nm)) + table.GetRowData().AddArray(vtk_array_x_axis) + table.GetRowData().AddArray(array_points) + points = self.chart.AddPlot(vtk.vtkChart.LINE) + points.SetInputData(table, 0, 1) + points.SetColor(0, 0, 0, 255) + + def handle_remove_offset(self, image, line_points, remove_offset_method): + if remove_offset_method == "whole_image": + line_points -= np.nanmin(image) + elif remove_offset_method == "line_profile_range": + line_points -= min(line_points) + return line_points diff --git a/view/main_window.py b/view/main_window.py new file mode 100644 index 0000000..60963e7 --- /dev/null +++ b/view/main_window.py @@ -0,0 +1,1887 @@ +import ast +import logging +import math +import os +import time + +from PyQt5 import QtWidgets, QtTest, QtCore, QtGui + +from model_view import frontend_adapter +from view import export_dialog, filter_dialog_2d, filter_dialog_1d, settings_dialog, import_h5_dialog, \ + import_gwy_dialog, progress_representations, export_zip_dialog, analyse_dialog, saasmi_dialog, \ + import_feature_dialog, measuring_points_dialog, rescale_feature_dialog, line_profile_dialog, \ + feature_class_visualization_dialog, jump_visualization_dialog, average_image_dialog, feature_rag_dialog, \ + contrast_dialog, feature_grid_dialog, dialog_neural_networks, point_cloud_dialog, import_npy_dialog, density_dialog +from view import plotable_dialog +from view.image_visualization import qt_render_window_factory +from view.image_visualization.colors import feature_class_colors +from view.image_visualization.vtk.rendering_methods import feature_visualization_vtk +from view.qt_custom_dialogs import rename_dialog +from view.qt_custom_widgets import right_click_menu +from view.saasmi_visualization.matplotlib import saasmi_prepared_images +from view.ui import ui_mainwindow + +log = logging.getLogger(__name__) + + +class MainWindow(QtWidgets.QMainWindow): + log_message_received_signal = QtCore.pyqtSignal(str) + + def __init__(self, model_view, parent=None): + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + QtWidgets.QMainWindow.__init__(self, parent) + self.ui = ui_mainwindow.Ui_MainWindow() + self.ui.setupUi(self) + self.setAcceptDrops(True) + self.add_left_click_menu_to_feature_classes_list_widget() + self.ui.widget_window_1_settings.sizePolicy().setVerticalPolicy(QtWidgets.QSizePolicy.Maximum) + self.ui.widget_window_2_settings.sizePolicy().setVerticalPolicy(QtWidgets.QSizePolicy.Maximum) + log.info("MainWindow started") + self.selected_feature_dict = {} + self.ui.splitter_render_windows.setSizes([1000000, 1000000]) + self.ui.splitter_render_window_1.setStretchFactor(1, 1) + self.ui.splitter_render_window_2.setStretchFactor(1, 1) + self.generate_window_widgets() + self.generate_render_windows() + window_1_settings_horizontal_stretch = self.ui.widget_window_1_settings.sizePolicy().horizontalStretch() + self.ui.widget_window_2_settings.sizePolicy().setHorizontalStretch(window_1_settings_horizontal_stretch) + self.showMaximized() + progress_representation = progress_representations.ProgressDialogQt(self, self.model_view) + self.model_view.set_progress_representation(progress_representation) + self.ui.spinBox_background_r.setValue(100) + self.ui.spinBox_background_g.setValue(100) + self.ui.spinBox_background_b.setValue(100) + self.ui.horizontalSlider_window_1.valueChanged.connect(self.handle_window_1_image_changed) + self.ui.horizontalSlider_window_2.valueChanged.connect(self.update_render_windows) + self.ui.pushButtonStartAnimation.clicked.connect(self.toggle_animation) + self.ui.actionImport_h5.triggered.connect(self.open_import_h5_dialog) + self.ui.actionImport_vsy.triggered.connect(self.load_from_vsy) + self.ui.actionImport_from_image_file.triggered.connect(self.load_from_image) + self.ui.actionImport_arn.triggered.connect(self.load_from_arn) + self.ui.actionImport_stp.triggered.connect(self.load_from_stp) + self.ui.actionExport_as_video.triggered.connect(self.handle_export_dialog) + self.ui.actionExport_as_image.triggered.connect(self.handle_export_dialog) + self.ui.actionExport_as_yspar_dat_mask_dat.triggered.connect(self.handle_export_dialog) + self.ui.pushButton_filter_full_1d_signal.clicked.connect(self.open_filter_1d_full_signal_dialog) + self.ui.pushButton_open_filter_dialog_1d.clicked.connect(self.open_filter_1d_dialog) + self.ui.pushButton_open_filter_dialog_2d.clicked.connect(self.open_filter_2d_dialog) + self.ui.actionExport_as_Scan_Object_vsy.triggered.connect(self.save_current_scan) + self.ui.radioButton_contrast_absolute.clicked.connect(self.change_contrast) + self.ui.radioButton_contrast_percentile.clicked.connect(self.change_contrast) + self.ui.radioButton_full_range.clicked.connect(self.change_contrast) + self.ui.doubleSpinBox_contrast_percentile_max.valueChanged.connect(self.change_contrast) + self.ui.doubleSpinBox_contrast_percentile_min.valueChanged.connect(self.change_contrast) + self.ui.doubleSpinBox_contrast_absolute_min.valueChanged.connect(self.change_contrast) + self.ui.doubleSpinBox_contrast_absolute_max.valueChanged.connect(self.change_contrast) + self.ui.tabWidget_visualization.currentChanged.connect(self.handle_visualization_method_changed) + self.ui.pushButton_show_average_image.clicked.connect(self.handle_show_average_image) + self.ui.checkBox_fourier_logarithmic.clicked.connect(self.handle_visualization_method_changed) + self.pre_visualization_method_str = 'image' + self.visualization_parameters = {} + self.animation_running = False + self.ui.spinBox_canny_sigma.setHidden(True) + self.ui.label_canny_sigma.setHidden(True) + self.ui.tabWidget_controller.setCurrentIndex(0) + self.ui.tabWidget_controller.setEnabled(False) + self.ui.comboBox_contours.currentIndexChanged.connect(self.handle_visualization_method_changed) + self.ui.comboBox_compare_method.currentIndexChanged.connect(self.handle_visualization_method_changed) + self.ui.spinBox_error_map_compare_image_number.valueChanged.connect(self.handle_visualization_method_changed) + self.ui.comboBox_fourier_method.currentIndexChanged.connect(self.handle_visualization_method_changed) + self.ui.pushButton_visualization_contours_apply.clicked.connect(self.handle_contours_tab_logic) + self.ui.actionSettings.triggered.connect(self.open_settings_dialog) + self.ui.actionImport_filter_from_log.triggered.connect(self.import_filter_log_file) + self.ui.checkBox_show_original_image.clicked.connect(self.toggle_show_original_image) + self.ui.actionGenerate_Log_File.triggered.connect(self.generate_log_file) + self.ui.actionImport_sxm.triggered.connect(self.load_from_sxm) + self.ui.comboBox_scan_window_2.currentIndexChanged.connect(self.handle_second_render_window_combo_box) + self.ui.comboBox_scan_window_1.currentIndexChanged.connect(self.handle_first_render_window_combo_box) + self.ui.actionImport_gwy.triggered.connect(self.handle_import_gwy) + self.ui.actionImport_npy.triggered.connect(self.open_import_npy_dialog) + self.ui.checkBox_image_invert.clicked.connect(self.handle_visualization_method_changed) + self.ui.comboBox_set_current_scan.currentIndexChanged.connect(self.handle_set_current_scan_combo_box) + self.ui.checkBox_snychronise.toggled.connect(self.handle_synchronise_checkbox) + self.ui.spinBox_background_r.valueChanged.connect(self.change_background_color) + self.ui.spinBox_background_g.valueChanged.connect(self.change_background_color) + self.ui.spinBox_background_b.valueChanged.connect(self.change_background_color) + self.ui.doubleSpinBox_background_alpha.valueChanged.connect(self.change_background_color) + self.ui.spinBox_NaN_R.valueChanged.connect(self.change_nan_color) + self.ui.spinBox_NaN_G.valueChanged.connect(self.change_nan_color) + self.ui.spinBox_NaN_B.valueChanged.connect(self.change_nan_color) + self.ui.comboBox_scan_image_class.currentIndexChanged.connect(self.handle_scan_direction_combobox) + self.ui.doubleSpinBox_NaN_A.valueChanged.connect(self.change_nan_color) + self.ui.actionExport_as_gwy.triggered.connect(self.handle_export_dialog) + self.ui.pushButton_add_feature_class.clicked.connect(self.handle_add_feature_class_button) + self.ui.listWidget_feature_classes.currentItemChanged.connect(self.update_feature_list_widget) + self.ui.listWidget_features.currentItemChanged.connect(self.update_feature_point_list) + self.ui.pushButton_remove_feature_class.clicked.connect(self.handle_remove_feature_class) + self.ui.pushButton_remove_feature.clicked.connect(self.handle_remove_feature) + self.ui.pushButton_remove_feature_point.clicked.connect(self.handle_remove_point) + self.ui.radioButton_pick_method_pick.clicked.connect(self.handle_change_feature_pick_method) + self.ui.radioButton_pick_method_point.clicked.connect(self.handle_change_feature_pick_method) + self.ui.radioButton_pick_method_feature.clicked.connect(self.handle_change_feature_pick_method) + self.ui.pushButton_save_feature_tracker_list.clicked.connect(self.handle_save_feature_list_button) + self.ui.pushButton_load_feature_tracker_list.clicked.connect(self.handle_load_feature_list_button) + self.ui.comboBox_set_current_scan.currentIndexChanged.connect(self.ui.comboBox_scan_window_1.setCurrentIndex) + self.ui.comboBox_scan_window_1.currentIndexChanged.connect(self.ui.comboBox_set_current_scan.setCurrentIndex) + self.ui.actionExport_as_zip_file.triggered.connect(self.handle_export_as_zip) + self.ui.actionImport_scan_zip.triggered.connect(self.handle_import_scan_zip) + self.ui.pushButton_set_feature_class_color.clicked.connect(self.handle_feature_class_color_button) + self.ui.spinBox_maximum_number_of_points_per_feature.valueChanged.connect( + self.handle_maximum_number_of_points_per_feature) + self.ui.listWidget_feature_classes.clicked.connect(self.feature_class_list_widget_clicked) + self.ui.listWidget_features.clicked.connect(self.handle_features_list_widget_clicked) + self.ui.listWidget_feature_points.clicked.connect(self.handle_feature_points_list_widget_clicked) + self.ui.pushButton_auto_detect_features_current_image.clicked.connect( + self.handle_auto_detect_features_current_image) + self.ui.pushButton_auto_detect_features_all_images.clicked.connect(self.handle_auto_detect_features_all_images) + self.ui.pushButton_feature_analyse.clicked.connect(self.handle_open_feature_analyse_dialog) + self.ui.pushButton_generate_drift_feature.clicked.connect(self.handle_generate_drift_feature) + self.ui.pushButton_drift_correction_by_feature.clicked.connect(self.handle_correct_drift_by_feature) + self.disable_render_windows_update = False + self.ui.lineEdit_feature_class_to_add.textChanged.connect(self.handle_feature_class_line_edit_changed) + self.ui.pushButton_scan_delete.clicked.connect(self.handle_delete_scan_button) + self.ui.pushButton_auto_detect_feature_clear_image.clicked.connect(self.handle_feature_clear_image_button) + self.ui.actionImport_avi.triggered.connect(self.handle_import_avi) + self.ui.action_reset_horizontal_layout.triggered.connect(self.handle_reset_horizontal_layout) + self.ui.pushButton_saasmi.clicked.connect(self.handle_saasmi_button) + self.set_up_saasmi_ui() + self.ui.spinBox_number_of_neighbors.valueChanged.connect(self.handle_saasmi_number_of_neighbors) + self.ui.doubleSpinBox_saasmi_disk_size.valueChanged.connect(self.handle_saasmi_disk_size) + self.ui.pushButton_ransac_correct_drift.clicked.connect(self.handle_ransac_button) + self.ui.pushButton_clear_logs.clicked.connect(self.clear_logs) + self.ui.pushButton_delete_current_image_class.clicked.connect(self.handle_delete_image_class_button) + self.ui.checkBox_show_rag_in_image.toggled.connect(self.handle_show_rag_checkbox) + self.ui.checkBox_show_features.toggled.connect(self.handle_show_feature_checkbox) + self.ui.pushButton_save_feature_settings_as_default.clicked.connect(self.apply_feature_controller_settings) + self.last_update = None + self.model_view.add_custom_logging_function(self.logging_callback_function) + self.load_default_feature_settings() + self.log_message_received_signal.connect(self.add_logging_message) + self.ui.checkBox_enable_autosave.toggled.connect(self.handle_auto_save_checkbox) + self.ui.spinBox_autosave_in_seconds.setValue( + self.model_view.get_config_by_key_sub_key('program_settings', 'auto_save_interval')) + self.ui.checkBox_enable_autosave.setChecked( + self.model_view.get_config_by_key_sub_key('program_settings', 'auto_save')) + self.ui.spinBox_autosave_in_seconds.valueChanged.connect(self.handle_auto_save_checkbox) + self.ui.pushButton_show_measuring_points.clicked.connect(self.handle_show_measuring_points_dialog) + self.ui.pushButton_show_density_plot.clicked.connect(self.handle_show_density_dialog) + self.ui.pushButton_step_size_update.clicked.connect(self.handle_step_size_update_button) + self.ui.pushButton_rescale_features.clicked.connect(self.handle_open_rescale_feature_dialog) + self.ui.pushButton_open_line_profile_dialog.clicked.connect(self.handle_open_line_profile_dialog) + self.ui.pushButton_saasmi_show_prepared_images.clicked.connect(self.handle_show_saasmi_prepared_images) + self.ui.pushButton_jump_visualization.clicked.connect(self.handle_open_jump_visualization_dialog) + self.ui.pushButton_feature_index_check_all.clicked.connect(self.handle_feature_index_check_all) + self.ui.pushButton_feature_index_uncheck_all.clicked.connect(self.handle_feature_index_uncheck_all) + self.ui.pushButton_feature_class_check_all.clicked.connect(self.handle_feature_class_check_all) + self.ui.pushButton_feature_class_uncheck_all.clicked.connect(self.handle_feature_class_uncheck_all) + self.ui.pushButton_open_neighbor_information_dialog.clicked.connect( + self.handle_feature_rag_dialog) + self.ui.pushButton_generate_feature_grid.clicked.connect(self.handle_generate_feature_grid) + self.ui.pushButton_open_contrast_dialog.clicked.connect(self.handle_contrast_dialog) + self.ui.pushButton_apply_feature_grid_to_feature_points.clicked.connect( + self.handle_apply_feature_grid_to_features) + self.ui.checkBox_use_feature_grid.clicked.connect(self.handle_feature_grid_check_box) + self.ui.checkBox_show_feature_grid.clicked.connect(self.handle_show_feature_grid_check_box) + self.ui.checkBox_use_feature_grid_2.clicked.connect(self.handle_feature_grid_check_box) + self.ui.checkBox_use_feature_grid_2.stateChanged.connect(self.ui.checkBox_use_feature_grid.setChecked) + self.ui.checkBox_use_feature_grid.stateChanged.connect(self.ui.checkBox_use_feature_grid_2.setChecked) + self.ui.pushButton_generate_training_data.clicked.connect(self.handle_export_training_data) + self.ui.pushButton_drift_correction_by_phase_cross_correlation.clicked.connect( + self.handle_correct_drift_by_phase_cross_correlation) + self.set_up_phase_cross_correlation_method_combo_box() + self.ui.pushButton_load_yolo_label_folder.clicked.connect(self.handle_load_yolo_labels) + self.ui.pushButton_plot_h5_infos.clicked.connect(self.handle_plot_h5_info) + self.ui.pushButton_show_point_cloud.clicked.connect(self.handle_show_point_cloud_dialog) + self.ui.pushButton_export_lines_as_csv.clicked.connect(self.handle_export_lines_as_csv_button) + + def set_up_saasmi_ui(self): + self.ui.spinBox_number_of_neighbors.setValue(self.model_view.get_config_by_key_sub_key("saasmi", "k-neighbors")) + self.ui.checkBox_auto_generate_rag.setChecked( + self.model_view.get_config_by_key_sub_key("saasmi", "auto_generate_rag")) + self.ui.doubleSpinBox_saasmi_beta.setValue(self.model_view.get_config_by_key_sub_key("saasmi", "beta")) + self.ui.doubleSpinBox_saasmi_disk_size.setValue( + self.model_view.get_config_by_key_sub_key("saasmi", "disk_size")) + + def set_up_phase_cross_correlation_method_combo_box(self): + methods = ["constant drift", "pure cross correlation drift"] + methods_tool_tip = ["Calculates the most common drift and applies it to all images", + "Calculates the drift vector for each image"] + combo_box = self.ui.comboBox_phase_correlation_method_choser + combo_box.addItems(methods) + for index in range(len(methods)): + combo_box.setItemData(index, methods_tool_tip[index], QtCore.Qt.ToolTipRole) + + def clear_logs(self): + self.ui.textBrowser_logs.clear() + + def add_logging_message(self, message): + self.ui.textBrowser_logs.append(message) + + def logging_callback_function(self, message): + self.log_message_received_signal.emit(message) + + def generate_render_windows(self): + factory = qt_render_window_factory.RenderWindowFactory + main_window_type = self.model_view.get_config_by_key_sub_key('program_settings', 'main_render_window') + self.main_render_window = factory.get_render_window(main_window_type)( + widget=self.ui.widget_vtk_main_window, model_view=self.model_view, handle_features=True) + second_window_type = self.model_view.get_config_by_key_sub_key('program_settings', 'second_render_window') + self.second_render_window = factory.get_render_window(second_window_type)( + widget=self.ui.widget_vtk_second_window, + model_view=self.model_view, handle_features=True) + self.connect_main_render_window_with_feature_tracker() + + def connect_main_render_window_with_feature_tracker(self): + self.main_render_window.left_click_position_signal.connect(self.handle_vtk_window_left_click) + self.main_render_window.pick_method_changed_signal.connect(self.handle_feature_pick_method_changed) + self.main_render_window.next_image_signal.connect(self.handle_next_image_signal) + self.main_render_window.hover_signal.connect(self.handle_hover_position) + self.main_render_window.previous_image_signal.connect(self.handle_previous_image_signal) + self.main_render_window.right_click_position_signal.connect(self.handle_remove_nearest_point) + + def handle_vtk_window_left_click(self, position): + if self.ui.radioButton_pick_method_point.isChecked(): + self.handle_add_feature_point(position) + elif self.ui.radioButton_pick_method_feature.isChecked(): + self.handle_add_feature(position) + elif self.ui.radioButton_pick_method_pick.isChecked(): + self.handle_pick_nearest_point(position) + + def handle_hover_position(self, position): + image_number = self.ui.spinBox_window_1.value() + z_value = self.model_view.get_images_z_value(image_number, position) + if math.isnan(z_value): + return + text = "({}, {}, {})".format(*position[:2], z_value) + self.ui.label_cursor_position.setText(text) + + def handle_pick_nearest_point(self, position): + current_image_number = self.ui.spinBox_window_1.value() + feature_class_index, feature_index, point_index = self.model_view.get_nearest_point(position=position, + image_number=current_image_number) + if feature_class_index is not None and feature_index is not None and point_index is not None: + self.ui.listWidget_feature_classes.setCurrentRow(feature_class_index) + self.ui.listWidget_features.setCurrentRow(feature_index) + self.ui.listWidget_feature_points.setCurrentRow(point_index) + self.update_render_windows() + + def save_current_scan(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + last_file_path = self.model_view.get_config_by_key_sub_key("export_as_vsy", "export_path") + if last_file_path is None: + last_file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', last_file_path, "Visualyse(*.vsy)", + options=options) + if file_path == "": + return + elif file_path[-4:] != ".vsy": + file_path = file_path + ".vsy" + self.model_view.save_scan_in_file(file_path) + else: + self.model_view.save_scan_in_file(file_path) + self.model_view.set_config_by_key_sub_key("export_as_vsy", "export_path", file_path) + + def update_main_render_window(self, image_number=None): + self.generate_chosen_feature_dict() + show_rag = self.ui.checkBox_show_rag_in_image.isChecked() + show_features = self.ui.checkBox_show_features.isChecked() + if image_number is None: + image_number = self.ui.spinBox_window_1.value() + if self.ui.checkBox_fourier_logarithmic.isChecked(): + self.visualization_parameters['logarithmic'] = True + else: + self.visualization_parameters['logarithmic'] = False + scan_index = self.ui.comboBox_scan_window_1.currentIndex() + show_feature_grid = self.ui.checkBox_show_feature_grid.isChecked() + if scan_index >= 0: + color = self.model_view.get_background_color(scan_index) + self.main_render_window.set_background_color(color) + self.main_render_window.render(scan_index=scan_index, image_number=image_number, + feature_to_highlight=self.chosen_features, show_rag=show_rag, + show_features=show_features, show_feature_grid=show_feature_grid) + else: + self.main_render_window.reset_render_window() + self.main_render_window.update() + + def generate_chosen_feature_dict(self): + self.chosen_features = {} + try: + feature_class_name = self.ui.listWidget_feature_classes.currentItem().text() + self.chosen_features["feature_class_name"] = feature_class_name + except AttributeError: + return + feature_index = self.ui.listWidget_features.currentRow() + if feature_index < 0: + self.chosen_features["feature_index"] = None + else: + self.chosen_features["feature_index"] = feature_index + point_index = self.ui.listWidget_feature_points.currentRow() + if point_index < 0: + self.chosen_features["point_index"] = None + else: + self.chosen_features["point_index"] = point_index + + def toggle_show_original_image(self): + self.model_view.set_show_original_image(self.ui.checkBox_show_original_image.isChecked()) + self.update_render_windows() + + def toggle_animation(self): + if self.ui.pushButtonStartAnimation.text() == "Play": + self.ui.pushButtonStartAnimation.setText("Stop") + self.animation_loop() + else: + self.animation_running = False + + def animation_loop(self): + self.animation_running = True + while self.ui.horizontalSlider_window_1.value() < self.ui.horizontalSlider_window_1.maximum() and self.animation_running is True: + start_time = time.time() + self.ui.horizontalSlider_window_1.setValue(self.ui.horizontalSlider_window_1.value() + 1) + end_time = time.time() + wait_time = (1 / self.ui.doubleSpinBox_animation_fps.value()) - (end_time - start_time) + if wait_time < 0: + wait_time = 0 + QtTest.QTest.qWait(wait_time * 1000) + self.ui.pushButtonStartAnimation.setText("Play") + + def load_from_image(self): + directory = self.model_view.get_config_by_key_sub_key('import_image', 'file_path') + file_path = self.import_from_file(directory, file_type="Image Files (*.jpg *.jpeg *.png *.tif)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_image', 'file_path', file_path) + + def load_from_arn(self): + directory = self.model_view.get_config_by_key_sub_key('import_arn', 'file_path') + file_path = self.import_from_file(directory, file_type="Image Files (*.arn, *.ARN)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_arn', 'file_path', file_path) + + + def load_from_stp(self): + directory = self.model_view.get_config_by_key_sub_key('import_stp', 'file_path') + file_path = self.import_from_file(directory, file_type="wSXM File (*.stp)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_stp', 'file_path', file_path) + + def load_from_sxm(self): + directory = self.model_view.get_config_by_key_sub_key('import_sxm', 'file_path') + file_path = self.import_from_file(directory, file_type="SXM File (*.sxm)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_sxm', 'file_path', file_path) + self.scan_loaded() + + def load_from_vsy(self): + directory = self.model_view.get_config_by_key_sub_key('import_vsy', 'file_path') + file_path = self.import_from_file(directory, file_type="Visualyse File (*.vsy)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_vsy', 'file_path', file_path) + + def import_from_file(self, directory=None, file_type=""): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + if isinstance(directory, list): + directory = directory[0] + file_paths, _ = QtWidgets.QFileDialog.getOpenFileNames(self, "open file", directory, options=options, + filter=file_type) + if file_paths: + if self.model_view.load_files(file_paths=file_paths): + self.scan_loaded() + return file_paths + + def import_filter_log_file(self): + directory = self.model_view.get_config_by_key_sub_key("import_filter_log_file", "file_path") + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open file", directory, options=options) + if file_path: + self.model_view.generate_filter_list_from_log_file(file_path=file_path) + self.update_render_windows() + self.update_1d_filter_list() + self.update_1d_full_signal_filter_list() + self.update_2d_filter_list() + self.model_view.set_config_by_key_sub_key("import_filter_log_file", "file_path", file_path) + + def set_maximum_image_index(self, maximum_image_index, window=1): + if window == 1: + self.ui.spinBox_window_1.setMaximum(maximum_image_index) + self.ui.horizontalSlider_window_1.setMaximum(maximum_image_index) + elif window == 2: + self.ui.spinBox_window_2.setMaximum(maximum_image_index) + self.ui.horizontalSlider_window_2.setMaximum(maximum_image_index) + + def scan_loaded(self): + self.disable_render_windows_update = True + self.generate_random_color_button() + self.enable_scan_visualization() + self.update_all_scan_combo_boxes() + self.update_render_windows() + self.disable_render_windows_update = False + self.update_render_windows() + self.update_feature_class_list_widget() + self.main_render_window.reset_render_camera() + self.check_if_maximum_number_of_points_is_exceeded() + self.update_1d_filter_list() + self.update_1d_full_signal_filter_list() + self.update_2d_filter_list() + + def update_all_scan_combo_boxes(self): + self.update_first_render_window_combo_box() + self.update_second_render_window_combo_box() + self.update_set_current_scan_combo_box() + self.update_compare_scan_combo_box() + + def enable_scan_visualization(self): + self.ui.scrollArea_2.setEnabled(True) + self.ui.widget_first_window.setEnabled(True) + self.ui.widget_second_window.setEnabled(True) + self.ui.spinBox_window_1.setEnabled(True) + self.ui.horizontalSlider_window_1.setEnabled(True) + self.ui.pushButtonStartAnimation.setEnabled(True) + self.ui.menuExport.setEnabled(True) + self.ui.doubleSpinBox_animation_fps.setEnabled(True) + self.ui.groupBox_1d_signal_full.setEnabled(True) + self.ui.groupBox_filter_1d.setEnabled(True) + self.ui.listWidget_filter_1d.setEnabled(True) + self.ui.groupBox_filter_2d.setEnabled(True) + self.ui.listWidget_filter_2d.setEnabled(True) + self.ui.groupBox_visualization.setEnabled(True) + self.ui.actionSave_Scan.setEnabled(True) + self.ui.menuTools.setEnabled(True) + self.ui.menuFilter.setEnabled(True) + self.ui.actionGenerate_Log_File.setEnabled(True) + self.ui.comboBox_scan_window_2.setEnabled(True) + self.ui.groupBox_scan_direction.setEnabled(True) + self.ui.widget_current_scan.setEnabled(True) + self.ui.tabWidget_feature_tracker.setEnabled(True) + self.ui.tabWidget_feature_tracker.setCurrentIndex(0) + self.ui.groupBox_drift_detection.setEnabled(True) + self.ui.menuLayout.setEnabled(True) + self.ui.widget_vtk_main_window.setEnabled(True) + self.ui.widget_vtk_second_window.setEnabled(True) + self.ui.tabWidget_controller.setEnabled(True) + self.activate_delete_button() + + def _update_scan_combo_box(self, combo_box): + combo_box.clear() + self.add_scans_to_combo_box(combo_box) + combo_box.setCurrentIndex(combo_box.count() - 1) + + def update_first_render_window_combo_box(self): + self._update_scan_combo_box(self.ui.comboBox_scan_window_1) + + def update_second_render_window_combo_box(self): + combo_box_index = self.ui.comboBox_scan_window_2.currentIndex() + if combo_box_index < 0: + combo_box_index = 0 + self.ui.comboBox_scan_window_2.clear() + self.ui.comboBox_scan_window_2.addItem("None") + self.add_scans_to_combo_box(self.ui.comboBox_scan_window_2) + + self.ui.comboBox_scan_window_2.setCurrentIndex(combo_box_index) + + def update_set_current_scan_combo_box(self): + self._update_scan_combo_box(self.ui.comboBox_set_current_scan) + + def activate_delete_button(self): + if len(self.model_view.get_scan_list()) > 1: + self.ui.pushButton_scan_delete.setEnabled(True) + else: + self.ui.pushButton_scan_delete.setEnabled(False) + + def update_compare_scan_combo_box(self): + self._update_scan_combo_box(self.ui.comboBox_compare_scans) + + @QtCore.pyqtSlot() + def handle_export_dialog(self): + export_method = None + current_image_number = self.ui.spinBox_window_1.value() + if self.sender().objectName() == "actionExport_as_video": + export_method = "video" + elif self.sender().objectName() == "actionExport_as_image": + export_method = "image" + elif self.sender().objectName() == "actionExport_as_yspar_dat_mask_dat": + export_method = "RTSSTEM" + elif self.sender().objectName() == "actionExport_as_gwy": + export_method = "gwy" + export_dialog_ui = export_dialog.ExportDialog(self.model_view, export_method=export_method, + current_image_number=current_image_number) + export_dialog_ui.exec_() + self.update_render_windows() + self.update_feature_class_list_widget() + + def set_contrast_ui(self, visualization_settings): + try: + contrast_method = visualization_settings['contrast_method'] + contrast_parameters = visualization_settings['contrast_parameters'] + except AttributeError: + contrast_method = 'percentile' + contrast_parameters = [0, 99] + if contrast_method == 'percentile': + self.ui.radioButton_contrast_percentile.setChecked(True) + self.ui.doubleSpinBox_contrast_percentile_min.setValue(contrast_parameters[0]) + self.ui.doubleSpinBox_contrast_percentile_max.setValue(contrast_parameters[1]) + elif contrast_method == 'absolute': + self.ui.radioButton_contrast_absolute.setChecked(True) + self.ui.doubleSpinBox_contrast_absolute_min.setValue(contrast_parameters[0]) + self.ui.doubleSpinBox_contrast_absolute_max.setValue(contrast_parameters[1]) + else: + self.ui.radioButton_full_range.setChecked(True) + + def load_visualization_settings(self): + visualization_settings = self.model_view.get_visualization_settings() + self.update_scan_information() + self.update_step_size_fields() + self.setUpdatesEnabled(False) + self.set_contrast_ui(visualization_settings) + self.set_background_color(visualization_settings) + self.set_nan_color(visualization_settings) + self.set_logarithmic_range(visualization_settings) + self.set_visualization_method(visualization_settings) + self.set_scan_direction(visualization_settings) + self.update_filter_list_widget() + self.setUpdatesEnabled(True) + + def open_filter_1d_full_signal_dialog(self): + tmp_scan = self.model_view.get_current_scan() + use_visualyze_function = self.ui.checkBox_filter_1d_use_visualyze_function.isChecked() + try: + scan_test_filter = self.model_view.generate_1d_test_scan_from_scan(tmp_scan, None, + use_visualyze_function, + filter_type="1d_full_signal") + except AttributeError: + log.info("This image has no 1D Data") + return + self.model_view.set_current_scan(scan_test_filter) + maximum_number_of_points = self.ui.spinBox_full_signal_maximum_number_of_points.value() + self.filter_dialog_ui = filter_dialog_1d.FilterDialog1D(self.model_view, filter_type="1d_full_signal", + maximum_number_of_points=maximum_number_of_points) + self.filter_dialog_ui.showMaximized() + accepted = self.filter_dialog_ui.exec_() + if accepted: + self.model_view.apply_1d_filter_from_test_scan_on_real(scan_test_filter, tmp_scan, + apply_full_signal_filters=True) + self.model_view.set_current_scan(tmp_scan) + if accepted: + self.model_view.generate_filter_logs_file() + self.update_1d_full_signal_filter_list() + self.update_render_windows() + + def open_filter_1d_dialog(self): + current_image = self.ui.spinBox_window_1.value() + tmp_scan = self.model_view.get_current_scan() + use_visualyze_function = self.ui.checkBox_filter_1d_use_visualyze_function.isChecked() + try: + scan_test_filter = self.model_view.generate_1d_test_scan_from_scan(tmp_scan, current_image, + use_visualyze_function) + except AttributeError: + log.info("This image has no 1D Data") + return + self.model_view.set_current_scan(scan_test_filter) + self.filter_dialog_ui = filter_dialog_1d.FilterDialog1D(self.model_view) + self.filter_dialog_ui.showMaximized() + accepted = self.filter_dialog_ui.exec_() + if accepted: + self.model_view.apply_1d_filter_from_test_scan_on_real(scan_test_filter, tmp_scan) + self.model_view.set_current_scan(tmp_scan) + if accepted: + self.model_view.generate_filter_logs_file() + self.ui.listWidget_filter_1d.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(): + self.ui.listWidget_filter_1d.addItem(filter_str) + self.update_render_windows() + + def open_filter_2d_dialog(self): + current_image = self.ui.spinBox_window_1.value() + tmp_scan = self.model_view.get_current_scan() + use_visualyze_function = self.ui.checkBox_filter_2d_use_visualyze_function.isChecked() + scan_test_filter = self.model_view.generate_2d_test_scan_from_scan(tmp_scan, current_image, + use_visualyze_function=use_visualyze_function) + self.model_view.set_current_scan(scan_test_filter) + self.filter_dialog_ui = filter_dialog_2d.FilterDialog2D(self.model_view, scan_test_filter) + self.filter_dialog_ui.showMaximized() + accepted = self.filter_dialog_ui.exec_() + if accepted: + self.model_view.apply_2d_filter_from_test_scan_on_real(scan_test_filter, tmp_scan) + self.model_view.set_current_scan(tmp_scan) + if accepted: + self.model_view.generate_filter_logs_file() + self.update_2d_filter_list() + self.update_render_windows() + + def update_1d_full_signal_filter_list(self): + self.ui.listWidget_filter_full_1d_signal.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(filter_type="1d_full_signal"): + self.ui.listWidget_filter_full_1d_signal.addItem(filter_str) + + def update_1d_filter_list(self): + self.ui.listWidget_filter_1d.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(): + self.ui.listWidget_filter_1d.addItem(filter_str) + + def update_2d_filter_list(self): + self.ui.listWidget_filter_2d.clear() + for filter_str in self.model_view.get_2d_filter_str_filter_list(): + self.ui.listWidget_filter_2d.addItem(filter_str) + + def generate_window_widgets(self): + show_second_widget = self.model_view.get_config_by_key_sub_key("program_settings", 'show_second_window') + self.ui.widget_second_window.setHidden(not show_second_widget) + if not show_second_widget: + self.ui.checkBox_snychronise.setChecked(False) + + def open_settings_dialog(self): + settings_dialog_ui = settings_dialog.SettingsDialog(self.model_view, + current_image_number=self.ui.spinBox_window_1.value()) + if settings_dialog_ui.exec_(): + self.generate_window_widgets() + self.generate_render_windows() + self.update_render_windows() + self.update_feature_class_list_widget() + self.main_render_window.reset_render_camera() + self.second_render_window.reset_render_camera() + + def change_contrast(self): + method = None + contrast_min, contrast_max = None, None + if self.ui.radioButton_contrast_absolute.isChecked(): + method = "absolute" + contrast_min = self.ui.doubleSpinBox_contrast_absolute_min.value() + contrast_max = self.ui.doubleSpinBox_contrast_absolute_max.value() + elif self.ui.radioButton_contrast_percentile.isChecked(): + method = "percentile" + contrast_min = self.ui.doubleSpinBox_contrast_percentile_min.value() + contrast_max = self.ui.doubleSpinBox_contrast_percentile_max.value() + elif self.ui.radioButton_full_range.isChecked(): + method = None + + self.model_view.change_contrast_for_current_scan(method=method, contrast_min=contrast_min, + contrast_max=contrast_max) + self.update_render_windows() + + def handle_visualization_method_changed(self): + index = self.ui.tabWidget_visualization.currentIndex() + if index == 0: + self.handle_image_tab() + elif index == 1: + self.handle_fourier_tab() + elif index == 2: + self.handle_image_comparison_tab() + elif index == 3: + self.handle_contours_tab() + + def update_visualization_method(self): + self.visualization_parameters['logarithmic'] = self.ui.checkBox_fourier_logarithmic.isChecked() + self.model_view.set_visualization_method(type=self.pre_visualization_method_str, + parameters=self.visualization_parameters, + scan_index=self.ui.comboBox_set_current_scan.currentIndex()) + + def handle_image_tab(self): + if self.ui.checkBox_image_invert.isChecked(): + self.pre_visualization_method_str = 'image_invert' + else: + self.pre_visualization_method_str = 'image' + self.update_render_windows() + + def handle_fourier_tab(self): + index = self.ui.comboBox_fourier_method.currentIndex() + if index == 0: + self.pre_visualization_method_str = "fourier_magnitude" + elif index == 1: + self.pre_visualization_method_str = "fourier_phase" + self.update_render_windows() + + def handle_image_comparison_tab(self): + index = self.ui.comboBox_compare_method.currentIndex() + self.visualization_parameters['reference_image'] = self.ui.spinBox_error_map_compare_image_number.value() + self.visualization_parameters['reference_scan'] = self.ui.comboBox_compare_scans.currentText() + if index == 0: + self.pre_visualization_method_str = "ssim_map" + elif index == 1: + self.pre_visualization_method_str = "deviation_map" + elif index == 2: + self.pre_visualization_method_str = "cross_correlation" + elif index == 3: + self.pre_visualization_method_str = "background_subtraction_single_image" + elif index == 4: + self.pre_visualization_method_str = "background_subtraction_all_images" + self.update_render_windows() + + def open_import_h5_dialog(self, file_path=None): + if type(file_path) != str: + file_path = None + dialog_h5 = import_h5_dialog.ImportH5Dialog(self.model_view, file_path=file_path) + if dialog_h5.exec_(): + self.scan_loaded() + self.update_render_windows() + + def open_import_npy_dialog(self, file_path=None): + if type(file_path) != str: + file_path = None + dialog_npy = import_npy_dialog.ImportNpyDialog(self.model_view, file_path=file_path) + if dialog_npy.exec_(): + self.scan_loaded() + self.update_render_windows() + + def handle_import_gwy(self, file_path=None): + if type(file_path) != str: + file_path = None + dialog_gwy = import_gwy_dialog.ImportGWYDialog(self.model_view, file_path=file_path) + if dialog_gwy.exec_(): + self.update_render_windows() + self.scan_loaded() + + def handle_contours_tab(self): + try: + current_contours_index = self.current_contours_index + except AttributeError: + current_contours_index = None + if self.ui.comboBox_contours.currentIndex() != current_contours_index: + index = self.ui.comboBox_contours.currentIndex() + self.ui.label_blob_min_sigma.setHidden(True) + self.ui.spinBox_blob_min_sigma.setHidden(True) + self.ui.label_blob_max_sigma.setHidden(True) + self.ui.spinBox_blob_max_sigma.setHidden(True) + self.ui.label_blob_num_sigma.setHidden(True) + self.ui.spinBox_blob_num_sigma.setHidden(True) + self.ui.label_blob_threshold.setHidden(True) + self.ui.doubleSpinBox_blob_threshold.setHidden(True) + self.ui.label_blob_overlap.setHidden(True) + self.ui.doubleSpinBox_blob_overlap.setHidden(True) + self.ui.spinBox_canny_sigma.setHidden(True) + self.ui.label_canny_sigma.setHidden(True) + + if index == 0: + self.ui.spinBox_canny_sigma.setHidden(False) + self.ui.label_canny_sigma.setHidden(False) + elif index == 1 or index == 2 or index == 3: + if index == 1: + self.ui.label_blob_num_sigma.setHidden(False) + self.ui.spinBox_blob_num_sigma.setHidden(False) + self.ui.label_blob_min_sigma.setHidden(False) + self.ui.spinBox_blob_min_sigma.setHidden(False) + self.ui.label_blob_max_sigma.setHidden(False) + self.ui.spinBox_blob_max_sigma.setHidden(False) + self.ui.label_blob_threshold.setHidden(False) + self.ui.doubleSpinBox_blob_threshold.setHidden(False) + self.ui.label_blob_overlap.setHidden(False) + self.ui.doubleSpinBox_blob_overlap.setHidden(False) + self.handle_contours_tab_logic() + self.current_contours_index = self.ui.comboBox_contours.currentIndex() + + def handle_contours_tab_logic(self): + index = self.ui.comboBox_contours.currentIndex() + if index == 0: + self.pre_visualization_method_str = "canny_contours" + self.visualization_parameters["canny_sigma"] = self.ui.spinBox_canny_sigma.value() + elif index == 1 or index == 2 or index == 3: + if index == 1: + self.pre_visualization_method_str = "blob_log" + self.ui.label_blob_num_sigma.setHidden(False) + self.ui.spinBox_blob_num_sigma.setHidden(False) + elif index == 2: + self.pre_visualization_method_str = "blob_dog" + elif index == 3: + self.pre_visualization_method_str = "blob_doh" + self.ui.label_blob_min_sigma.setHidden(False) + self.ui.spinBox_blob_min_sigma.setHidden(False) + self.ui.label_blob_max_sigma.setHidden(False) + self.ui.spinBox_blob_max_sigma.setHidden(False) + self.ui.label_blob_threshold.setHidden(False) + self.ui.doubleSpinBox_blob_threshold.setHidden(False) + self.ui.label_blob_overlap.setHidden(False) + self.ui.doubleSpinBox_blob_overlap.setHidden(False) + self.visualization_parameters['blob_min_sigma'] = self.ui.spinBox_blob_min_sigma.value() + self.visualization_parameters['blob_max_sigma'] = self.ui.spinBox_blob_max_sigma.value() + self.visualization_parameters['blob_num_sigma'] = self.ui.spinBox_blob_num_sigma.value() + self.visualization_parameters['blob_threshold'] = self.ui.doubleSpinBox_blob_threshold.value() + self.visualization_parameters['blob_overlap'] = self.ui.doubleSpinBox_blob_overlap.value() + self.update_render_windows() + + def generate_log_file(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', "", "log file(*.log)", options=options) + if file_path[-4:] != ".log": + self.model_view.generate_log_file(file_path + ".log") + else: + self.model_view.generate_log_file(file_path) + + def handle_window_1_image_changed(self): + self.handle_auto_fill_images() + self.update_scan_information() + self.update_feature_class_list_widget() + self.update_render_windows() + + def update_render_windows(self): + if self.disable_render_windows_update == True: + return + if self.last_update is not None and self.last_updated - time.time() < 0.01: + return + self.update_visualization_method() + self.last_updated = time.time() + self.update_main_render_window() + self.update_second_render_window() + + def handle_second_render_window_combo_box(self): + self.set_maximum_image_index( + self.model_view.get_maximum_image_index_by_string(self.ui.comboBox_scan_window_2.currentText()), window=2) + self.update_render_windows() + self.second_render_window.reset_render_camera() + self.update_render_windows() + + def handle_first_render_window_combo_box(self): + self.ui.comboBox_set_current_scan.setCurrentIndex(self.ui.comboBox_scan_window_1.currentIndex()) + self.set_maximum_image_index( + self.model_view.get_maximum_image_index_by_string(self.ui.comboBox_scan_window_1.currentText()), window=1) + self.main_render_window.reset_render_camera() + self.update_render_windows() + + def update_second_render_window(self, image_number=None): + if image_number is None: + image_number = self.ui.spinBox_window_2.value() + scan_index = self.ui.comboBox_scan_window_2.currentIndex() - 1 + if scan_index >= 0: + background_color = self.model_view.get_background_color(scan_index) + self.second_render_window.set_background_color(background_color) + self.second_render_window.render(scan_index=scan_index, image_number=image_number) + else: + self.second_render_window.reset_render_window() + + def set_background_color(self, visualization_settings): + background_color = visualization_settings['background_color'] + self.ui.spinBox_background_r.setValue(int(background_color[0] * 255)) + self.ui.spinBox_background_g.setValue(int(background_color[1] * 255)) + self.ui.spinBox_background_b.setValue(int(background_color[2] * 255)) + self.ui.doubleSpinBox_background_alpha.setValue(background_color[3]) + + def set_nan_color(self, visualization_settings): + nan_color = visualization_settings['nan_color'] + self.ui.spinBox_NaN_R.setValue(int(nan_color[0] * 255)) + self.ui.spinBox_NaN_G.setValue(int(nan_color[1] * 255)) + self.ui.spinBox_NaN_B.setValue(int(nan_color[2] * 255)) + self.ui.doubleSpinBox_NaN_A.setValue(nan_color[3]) + + def set_logarithmic_range(self, visualization_settings): + logarithmic = visualization_settings['logarithmic'] + self.ui.checkBox_fourier_logarithmic.setChecked(logarithmic) + + def update_filter_list_widget(self): + self.ui.listWidget_filter_2d.clear() + for filter_str in self.model_view.get_2d_filter_str_filter_list(): + self.ui.listWidget_filter_2d.addItem(filter_str) + + self.ui.listWidget_filter_1d.clear() + for filter_str in self.model_view.get_1d_filter_str_filter_list(): + self.ui.listWidget_filter_1d.addItem(filter_str) + + def add_scans_to_combo_box(self, combo_box): + for scan in self.model_view.get_scan_list(): + combo_box.addItem(str(scan)) + + def handle_set_current_scan_combo_box(self): + self.ui.comboBox_scan_window_1.setCurrentIndex(self.ui.comboBox_set_current_scan.currentIndex()) + current_text = self.ui.comboBox_set_current_scan.currentText() + self.model_view.set_current_scan_by_string(current_text) + self.ui.spinBox_error_map_compare_image_number.setMaximum( + self.model_view.get_maximum_image_index_by_string(current_text)) + self.ui.spinBox_ransac_reference_image_number.setMaximum( + self.model_view.get_maximum_image_index_by_string(current_text)) + self.load_visualization_settings() + + def handle_synchronise_checkbox(self): + if self.ui.checkBox_snychronise.isChecked(): + self.ui.horizontalSlider_window_2.setValue(self.ui.horizontalSlider_window_1.value()) + self.ui.horizontalSlider_window_1.valueChanged['int'].connect(self.ui.spinBox_window_2.setValue) + self.ui.horizontalSlider_window_2.valueChanged['int'].connect(self.ui.spinBox_window_1.setValue) + else: + self.ui.horizontalSlider_window_1.valueChanged['int'].disconnect(self.ui.spinBox_window_2.setValue) + self.ui.horizontalSlider_window_2.valueChanged['int'].disconnect(self.ui.spinBox_window_1.setValue) + + def set_visualization_method(self, visualization_settings): + visualization_method = visualization_settings['visualization_method'] + visualization_parameters = visualization_settings['visualization_parameters'] + if visualization_method == 'image': + self.ui.tabWidget_visualization.setCurrentIndex(0) + self.ui.checkBox_image_invert.setChecked(False) + elif visualization_method == 'image_invert': + self.ui.tabWidget_visualization.setCurrentIndex(0) + self.ui.checkBox_image_invert.setChecked(True) + elif visualization_method == 'fourier_magnitude': + self.ui.tabWidget_visualization.setCurrentIndex(1) + self.ui.comboBox_fourier_method.setCurrentIndex(0) + elif visualization_method == 'fourier_phase': + self.ui.tabWidget_visualization.setCurrentIndex(1) + self.ui.comboBox_fourier_method.setCurrentIndex(1) + elif visualization_method == 'ssim_map': + self.ui.tabWidget_visualization.setCurrentIndex(2) + self.ui.comboBox_compare_method.setCurrentIndex(0) + self.ui.spinBox_error_map_compare_image_number.setValue(visualization_parameters['reference_image_number']) + elif visualization_method == 'deviation_map': + self.ui.tabWidget_visualization.setCurrentIndex(2) + self.ui.comboBox_compare_method.setCurrentIndex(1) + self.ui.spinBox_error_map_compare_image_number.setValue(visualization_parameters['reference_image_number']) + elif visualization_method == 'cross_correlation': + self.ui.tabWidget_visualization.setCurrentIndex(2) + self.ui.comboBox_compare_method.setCurrentIndex(2) + self.ui.spinBox_error_map_compare_image_number.setValue(visualization_parameters['reference_image_number']) + elif visualization_method == 'canny_contours': + self.ui.tabWidget_visualization.setCurrentIndex(3) + self.ui.comboBox_contours.setCurrentIndex(0) + self.ui.spinBox_canny_sigma.setValue(visualization_parameters['sigma']) + elif visualization_method == 'blob_log': + self.ui.tabWidget_visualization.setCurrentIndex(3) + self.ui.comboBox_contours.setCurrentIndex(1) + self.ui.spinBox_blob_min_sigma.setValue(visualization_parameters['min_sigma']) + self.ui.spinBox_blob_max_sigma.setValue(visualization_parameters['max_sigma']) + self.ui.spinBox_blob_num_sigma.setValue(visualization_parameters['num_sigma']) + self.ui.doubleSpinBox_blob_threshold.setValue(visualization_parameters['threshold']) + self.ui.doubleSpinBox_blob_overlap.setValue(visualization_parameters['threshold']) + elif visualization_method == 'blob_dog': + self.ui.tabWidget_visualization.setCurrentIndex(3) + self.ui.comboBox_contours.setCurrentIndex(2) + self.ui.spinBox_blob_min_sigma.setValue(visualization_parameters['min_sigma']) + self.ui.spinBox_blob_max_sigma.setValue(visualization_parameters['max_sigma']) + self.ui.doubleSpinBox_blob_threshold.setValue(visualization_parameters['threshold']) + self.ui.doubleSpinBox_blob_overlap.setValue(visualization_parameters['threshold']) + elif visualization_method == 'blob_doh': + self.ui.tabWidget_visualization.setCurrentIndex(3) + self.ui.comboBox_contours.setCurrentIndex(3) + self.ui.spinBox_blob_min_sigma.setValue(visualization_parameters['min_sigma']) + self.ui.spinBox_blob_max_sigma.setValue(visualization_parameters['max_sigma']) + self.ui.doubleSpinBox_blob_threshold.setValue(visualization_parameters['threshold']) + self.ui.doubleSpinBox_blob_overlap.setValue(visualization_parameters['threshold']) + + def change_background_color(self): + r = self.ui.spinBox_background_r.value() + g = self.ui.spinBox_background_g.value() + b = self.ui.spinBox_background_b.value() + a = self.ui.doubleSpinBox_background_alpha.value() + self.model_view.change_background_color_for_current_scan(r, g, b, a) + self.update_render_windows() + + def change_nan_color(self): + r = self.ui.spinBox_NaN_R.value() + g = self.ui.spinBox_NaN_G.value() + b = self.ui.spinBox_NaN_B.value() + a = self.ui.doubleSpinBox_NaN_A.value() + self.model_view.set_nan_color(r, g, b, a) + self.update_render_windows() + + def handle_scan_direction_combobox(self): + self.model_view.set_scan_current_image_class(self.ui.comboBox_scan_image_class.currentText()) + self.update_scan_information() + self.update_1d_filter_list() + self.update_2d_filter_list() + self.handle_first_render_window_combo_box() + self.handle_second_render_window_combo_box() + self.update_feature_class_list_widget() + self.update_render_windows() + + def set_scan_direction(self, visualization_settings): + self.ui.comboBox_scan_image_class.clear() + if 'image_classes' in visualization_settings: + image_classes = visualization_settings['image_classes'] + for image_class in image_classes: + self.ui.comboBox_scan_image_class.addItem(image_class) + if 'current_image_class' in visualization_settings: + self.ui.comboBox_scan_image_class.setCurrentText(visualization_settings['current_image_class']) + else: + self.ui.comboBox_scan_image_class.setCurrentText('In and Out combined') + + def handle_add_feature_class_button(self): + feature_class_name = self.ui.lineEdit_feature_class_to_add.text() + color = self.get_button_color(self.ui.pushButton_set_feature_class_color) + self.handle_add_feature_class(feature_class_name=feature_class_name, color=color) + self.update_feature_class_list_widget() + + def handle_add_feature_class(self, feature_class_name=None, color=None): + if feature_class_name not in self.model_view.get_feature_classes_list(): + self.model_view.add_feature_class(feature_class=feature_class_name, color=color) + self.update_feature_class_list_widget() + self.ui.listWidget_feature_classes.setCurrentRow(self.ui.listWidget_feature_classes.count() - 1) + self.generate_random_color_button() + else: + self.model_view.set_feature_class_color(feature_class_name, color) + self.update_feature_class_list_widget() + + def handle_add_feature(self, position=None): + if self.ui.listWidget_feature_classes.count() == 0: + color = self.get_button_color(self.ui.pushButton_set_feature_class_color) + self.handle_add_feature_class(feature_class_name=self.ui.lineEdit_feature_class_to_add.text(), color=color) + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if current_feature_class_index < 0: + self.ui.listWidget_feature_classes.setCurrentRow(0) + current_feature_class_index = 0 + self.model_view.add_feature_to_feature_class(current_feature_class_index) + self.update_feature_list_widget() + self.ui.listWidget_features.setCurrentRow(self.ui.listWidget_features.count() - 1) + if position is not None: + self.handle_add_feature_point(position=position) + + def handle_add_feature_point(self, position=None): + if position is None: + return + if self.ui.listWidget_features.count() == 0: + self.handle_add_feature() + maximum_number_of_points = self.ui.spinBox_maximum_number_of_points_per_feature.value() + if self.ui.listWidget_feature_points.count() == maximum_number_of_points: + self.handle_add_feature() + feature_class_index = self.ui.listWidget_feature_classes.currentRow() + feature_index = self.ui.listWidget_features.currentRow() + image_number = self.ui.spinBox_window_1.value() + if self.ui.checkBox_use_feature_grid.isChecked(): + position = self.model_view.get_feature_grid_nearest_position(position=position) + + if self.ui.checkBox_add_to_all_images.isChecked() is False: + point_added = self.model_view.add_point_to_feature(feature_class_index=feature_class_index, + feature_index=feature_index, + position=position, image_number=image_number, + maximum_number_of_points=maximum_number_of_points) + if point_added == False: + log.info( + "No Point was added because the feature already has the maximum number of points, changed method to adding features") + else: + self.model_view.add_point_to_feature_for_all_images(feature_class_index=feature_class_index, + feature_index=feature_index, + position=position) + self.save_disable_render_for_function(self.update_feature_point_list) + self.ui.listWidget_feature_points.setCurrentRow(self.ui.listWidget_feature_points.count() - 1) + self.update_feature_class_list_widget() + + def handle_maximum_number_of_points_per_feature(self): + self.check_if_maximum_number_of_points_is_exceeded() + + def check_if_maximum_number_of_points_is_exceeded(self): + maximum_number_of_points = self.ui.spinBox_maximum_number_of_points_per_feature.value() + if maximum_number_of_points == 0: + return + maximum_number_of_points_exceeded_list = self.model_view.get_features_exceed_maximum_number_of_points( + maximum_number_of_points) + if len(maximum_number_of_points_exceeded_list) == 0: + return + logging_string = "Too many points in Feature Classes:\n{:>30s} {:>15s} {:>15s}".format("Feature Class", + "Feature Index", + "Image Number") + for maximum_number_of_points_exceeded in maximum_number_of_points_exceeded_list: + feature_class_name, feature_point_index, image_number = maximum_number_of_points_exceeded + logging_string += "\n{:>30s} {:>15s} {:>15s}".format(str(feature_class_name), str(feature_point_index), + str(image_number)) + log.info(logging_string) + + def update_feature_class_list_widget(self): + current_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_index = self.ui.listWidget_features.currentRow() + current_point_index = self.ui.listWidget_feature_points.currentRow() + self.ui.listWidget_feature_classes.clear() + feature_class_list = self.model_view.get_feature_classes_list() + self.ui.listWidget_feature_classes.addItems(feature_class_list) + self.save_set_current_row(self.ui.listWidget_feature_classes, current_class_index) + self.save_set_current_row(self.ui.listWidget_features, current_feature_index) + self.save_set_current_row(self.ui.listWidget_feature_points, current_point_index) + self.set_list_widget_feature_class_color() + self.update_feature_list_widget() + + def feature_class_list_widget_clicked(self): + self.ui.listWidget_features.setCurrentRow(-1) + self.ui.listWidget_feature_points.setCurrentRow(-1) + feature_class_name = self.ui.listWidget_feature_classes.currentItem().text() + self.ui.lineEdit_feature_class_to_add.setText(feature_class_name) + color = self.model_view.get_feature_class_color_by_name(feature_class_name) + self.set_button_color(self.ui.pushButton_set_feature_class_color, color) + self.update_render_windows() + + def update_feature_list_widget(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_index = self.ui.listWidget_features.currentRow() + current_point_index = self.ui.listWidget_feature_points.currentRow() + selected_feature_list = self.get_selected_feature_list() + self.ui.listWidget_features.clear() + if current_feature_class_index >= 0: + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=current_feature_class_index) + if len(feature_list) < 500: + self.generate_feature_list_with_checkboxes(feature_list, selected_feature_list, + current_feature_class_index) + else: + self.generate_feature_list_without_checkboxes(feature_list) + self.ui.lineEdit_feature_class_to_add.setText( + self.model_view.get_feature_classes_list()[current_feature_class_index]) + self.save_set_current_row(self.ui.listWidget_features, current_feature_index) + self.save_set_current_row(self.ui.listWidget_feature_points, current_point_index) + self.update_feature_point_list() + + def generate_feature_list_without_checkboxes(self, feature_list): + self.selected_feature_dict = {} + self.ui.listWidget_features.addItems(feature_list) + + def generate_feature_list_with_checkboxes(self, feature_list, selected_feature_list, + current_feature_class_index): + for feature in feature_list: + # Create widget + # Add widget to QListWidget funList + layout = QtWidgets.QHBoxLayout() + label = QtWidgets.QLabel(feature) + check_box = QtWidgets.QCheckBox() + check_box.clicked.connect(self.handle_select_feature_check_box_clicked) + if feature in selected_feature_list: + check_box.setChecked(True) + layout.addWidget(label) + layout.addWidget(check_box) + layout.addStretch() + list_widget_item = QtWidgets.QListWidgetItem() + widget = QtWidgets.QWidget() + layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + widget.setLayout(layout) + list_widget_item.setSizeHint(widget.sizeHint()) + self.ui.listWidget_features.addItem(list_widget_item) + self.ui.listWidget_features.setItemWidget(list_widget_item, widget) + + def update_feature_point_list(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_index = self.ui.listWidget_features.currentRow() + current_point_index = self.ui.listWidget_feature_points.currentRow() + image_number = self.ui.spinBox_window_1.value() + self.ui.listWidget_feature_points.clear() + if self.ui.listWidget_features.count() == 0 or current_feature_index < 0 or current_feature_class_index < 0: + return + point_list = self.model_view.get_feature_points(feature_class_index=current_feature_class_index, + feature_index=current_feature_index, + image_number=image_number) + point_str_list = [] + for point in point_list: + point_str = " ".join(["{:.4g}".format(coord) for coord in point[:2]]) + point_str_list.append(point_str) + self.ui.listWidget_feature_points.addItems(point_str_list) + self.save_set_current_row(self.ui.listWidget_feature_points, current_point_index) + self.update_render_windows() + + def handle_remove_feature_class(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if current_feature_class_index >= 0: + self.model_view.remove_feature_class(current_feature_class_index) + if self.ui.listWidget_feature_classes.count() - 1 >= current_feature_class_index: + self.ui.listWidget_feature_classes.setCurrentRow(current_feature_class_index) + elif self.ui.listWidget_feature_classes.count() > 0: + self.ui.listWidget_feature_classes.setCurrentRow(self.ui.listWidget_feature_classes.count() - 1) + self.update_render_windows() + self.update_feature_class_list_widget() + + def handle_remove_feature(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_index = self.ui.listWidget_features.currentRow() + if current_feature_index >= 0 and current_feature_class_index >= 0: + self.model_view.remove_feature(feature_class_index=current_feature_class_index, + feature_index=current_feature_index) + self.update_feature_class_list_widget() + self.update_render_windows() + + def handle_remove_point(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_index = self.ui.listWidget_features.currentRow() + point_index = self.ui.listWidget_feature_points.currentRow() + if current_feature_index >= 0 and current_feature_class_index >= 0 and point_index >= 0: + image_number = self.ui.spinBox_window_1.value() + self.model_view.remove_feature_point(feature_class_index=current_feature_class_index, + feature_index=current_feature_index, + point_index=point_index, image_number=image_number) + self.update_feature_class_list_widget() + self.update_render_windows() + + def handle_change_feature_pick_method(self): + pass + + def handle_feature_pick_method_changed(self, feature_pick_method): + if feature_pick_method == self.main_render_window.vtk_widget.FEATURE_PICK_MODE: + self.ui.radioButton_pick_method_feature.setChecked(True) + elif feature_pick_method == self.main_render_window.vtk_widget.POINT_PICK_MODE: + self.ui.radioButton_pick_method_point.setChecked(True) + else: + self.ui.radioButton_pick_method_pick.setChecked(True) + + def handle_next_image_signal(self): + current_image = self.ui.spinBox_window_1.value() + self.ui.spinBox_window_1.setValue(current_image + 1) + + def handle_previous_image_signal(self): + current_image = self.ui.spinBox_window_1.value() + if current_image > 0: + self.ui.spinBox_window_1.setValue(current_image - 1) + + def handle_remove_nearest_point(self, position): + image_number = self.ui.spinBox_window_1.value() + feature_class_index, feature_index = self.model_view.remove_nearest_feature_point(position, image_number) + self.ui.radioButton_pick_method_point.setChecked(True) + self.handle_change_feature_pick_method() + if feature_class_index is not None and feature_index is not None: + self.save_set_current_row(self.ui.listWidget_feature_classes, feature_class_index) + self.save_set_current_row(self.ui.listWidget_features, feature_index) + self.update_feature_class_list_widget() + self.update_render_windows() + + def handle_auto_fill_images(self): + current_image = self.ui.spinBox_window_1.value() + if self.ui.checkBox_feature_tracker_auto_fill.isChecked(): + self.model_view.auto_fill_feature_tracker(current_image) + elif self.ui.checkBox_auto_fill_selected_features.isChecked(): + selected_feature_dict = {} + # + for feature_class_name, feature_list in self.selected_feature_dict.items(): + selected_feature_dict[feature_class_name] = self.feature_list_str_to_feature_list_int(feature_list) + self.model_view.auto_fill_selected_features(current_image, selected_feature_dict) + + def feature_list_str_to_feature_list_int(self, feature_list): + feature_list_int = [] + for feature in feature_list: + feature_list_int.append(int(feature)) + return feature_list_int + + def generate_selected_feature_list(self): + current_feature_class_item = self.ui.listWidget_feature_classes.currentItem() + if current_feature_class_item is None: + return + else: + feature_class_name = current_feature_class_item.text() + feature_list = [] + for i in range(self.ui.listWidget_features.count()): + list_widget_item = self.ui.listWidget_features.item(i) + check_box = self.ui.listWidget_features.itemWidget(list_widget_item).children()[2] + label = self.ui.listWidget_features.itemWidget(list_widget_item).children()[1] + if check_box.isChecked(): + feature_list.append(label.text()) + self.selected_feature_dict[feature_class_name] = feature_list + + def get_selected_feature_list(self): + current_feature_class_item = self.ui.listWidget_feature_classes.currentItem() + if current_feature_class_item is None: + return None + else: + feature_class_name = current_feature_class_item.text() + try: + return self.selected_feature_dict[feature_class_name] + except KeyError: + return [] + + def save_set_current_row(self, list_widget, row): + if list_widget.count == 0: + list_widget.setCurrentRow(-1) + elif 0 <= row < list_widget.count(): + list_widget.setCurrentRow(row) + elif row >= list_widget.count(): + list_widget.setCurrentRow(list_widget.count() - 1) + + def set_list_widget_feature_class_color(self): + list_widget = self.ui.listWidget_feature_classes + for i in range(list_widget.count()): + feature_class_name = list_widget.item(i).text() + color = self.model_view.get_feature_class_color_by_name(feature_class_name) + if color is not None: + list_widget.item(i).setBackground(QtGui.QColor(*color)) + + def handle_save_feature_list_button(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', "", "(*.yaml)", options=options) + self.model_view.save_feature_tracker_in_file(file_path) + + def handle_load_feature_list_button(self): + feature_dialog = import_feature_dialog.ImportFeatureDialog(self.model_view) + if feature_dialog.exec_(): + file_path = self.model_view.get_config_by_key_sub_key("import_feature", "file_path") + feature_ratio = self.model_view.get_config_by_key_sub_key("import_feature", "feature_ratio") + if file_path: + reason_not_to_load = self.model_view.validate_feature_tracker_file(file_path) + reason_str = "" + for reason in reason_not_to_load: + reason_str = "{reason_str}\n {new_reason}".format(reason_str=reason_str, new_reason=reason) + if reason_not_to_load != []: + reply = QtWidgets.QMessageBox.question(self, 'Continue?', + reason_str, QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + return + self.model_view.load_feature_tracker_from_file(file_path, feature_ratio) + self.update_feature_class_list_widget() + self.update_render_windows() + + def handle_load_yolo_labels(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + directory_path = QtWidgets.QFileDialog.getExistingDirectory(self, "open directory", options=options) + self.model_view.load_yolo_label_directory(directory_path) + self.update_feature_class_list_widget() + + def handle_export_as_zip(self): + export_zip_file_dialog = export_zip_dialog.ExportZipDialog(model_view=self.model_view) + export_zip_file_dialog.exec_() + + def handle_import_scan_zip(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open file", options=options, + filter="Zip File (*.zip)") + if file_path: + if self.model_view.load_from_scan_zip_file(file_path=file_path): + self.scan_loaded() + return file_path + + def handle_feature_class_color_button(self): + color_dialog = QtWidgets.QColorDialog() + color_from_dialog = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog) + color = color_from_dialog.getRgb() + if color_from_dialog.isValid(): + self.set_button_color(self.ui.pushButton_set_feature_class_color, color) + current_feature_class_item = self.ui.listWidget_feature_classes.currentItem() + if current_feature_class_item is None: + return + else: + feature_class_name = current_feature_class_item.text() + if feature_class_name in self.model_view.get_feature_classes_list(): + self.model_view.set_feature_class_color(feature_class_name, color) + self.update_feature_class_list_widget() + self.update_render_windows() + + def set_button_color(self, button, color): + button.setStyleSheet("background-color : rgb({r},{g},{b})".format(r=color[0], g=color[1], b=color[2])) + + def get_button_color(self, button: QtWidgets.QPushButton): + color = button.palette().button().color() + color = color.getRgb() + return color + + def generate_random_color_button(self): + try: + self.color_index + except: + self.color_index = 0 + color = feature_class_colors.get_color_by_index(self.color_index) + self.set_button_color(self.ui.pushButton_set_feature_class_color, color) + self.color_index += 1 + + def handle_open_feature_analyse_dialog(self): + dialog = analyse_dialog.AnalyseDialog(self.model_view) + dialog.exec_() + del dialog + + def handle_auto_detect_features_current_image(self): + self.save_default_feature_settings() + image_number = self.ui.spinBox_window_1.value() + feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if feature_class_index < 0: + color = self.get_button_color(self.ui.pushButton_set_feature_class_color) + self.handle_add_feature_class(feature_class_name=self.ui.lineEdit_feature_class_to_add.text(), + color=color) + feature_class_index = 0 + self.model_view.auto_detect_features_in_image_and_add_to_feature_class(feature_class_index=feature_class_index, + image_number=image_number) + self.update_render_windows() + self.save_disable_render_for_function(function=self.update_feature_class_list_widget) + + def save_disable_render_for_function(self, function, args=None, kwargs=None): + if args is None: + args = [] + if kwargs is None: + kwargs = {} + prev_disable_status = self.disable_render_windows_update + self.disable_render_windows_update = True + return_value = function(*args, **kwargs) + self.disable_render_windows_update = prev_disable_status + return return_value + + def handle_auto_detect_features_all_images(self): + self.save_default_feature_settings() + if self.ui.spinBox_window_1.maximum() > 30: + reply = QtWidgets.QMessageBox.question(self, 'Continue?', + "Autodetecting features for this amount of images will take a long time, are you sure you want to continue?", + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + return + feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if feature_class_index < 0: + color = self.get_button_color(self.ui.pushButton_set_feature_class_color) + self.handle_add_feature_class(feature_class_name=self.ui.lineEdit_feature_class_to_add.text(), + color=color) + feature_class_index = 0 + self.model_view.auto_detect_features_in_all_images(feature_class_index=feature_class_index) + self.update_render_windows() + self.update_feature_class_list_widget() + + def handle_generate_drift_feature(self): + self.model_view.add_feature_class("Drift_Correction", (0, 255, 0)) + self.update_feature_class_list_widget() + self.update_render_windows() + self.update_feature_class_list_widget() + self.ui.tabWidget_controller.setCurrentWidget(self.ui.tab_feature) + + def handle_correct_drift_by_feature(self): + self.model_view.correct_drift_by_drift_feature_class() + self.ui.lineEdit_feature_class_to_add.setText("New Feature Class") + self.update_render_windows() + self.update_feature_class_list_widget() + + def handle_correct_drift_by_phase_cross_correlation(self): + method = self.ui.comboBox_phase_correlation_method_choser.currentText() + maximum_vector_length = self.ui.doubleSpinBox_max_drift_vector_length.value() + try: + image_offset_list = list(ast.literal_eval(self.ui.lineEdit_offset_list.text())) + except: + log.info("The image offset list has to be a python list, example: [0,1,2]") + return + + self.model_view.correct_drift_by_phase_cross_correlation(method, image_offset_list, maximum_vector_length) + self.update_render_windows() + + def handle_import_avi(self): + directory = self.model_view.get_config_by_key_sub_key('import_avi', 'file_path') + file_path = self.import_from_file(directory=directory, file_type="Audio Video Interleave File (*.avi)") + if file_path: + self.model_view.set_config_by_key_sub_key('import_avi', 'file_path', file_path) + + def handle_feature_class_line_edit_changed(self): + if self.model_view.get_feature_class_color_by_name(self.ui.lineEdit_feature_class_to_add.text()) is None: + color = self.model_view.get_color_by_name(self.ui.lineEdit_feature_class_to_add.text()) + if color is not None: + self.set_button_color(button=self.ui.pushButton_set_feature_class_color, color=color) + + def handle_delete_scan_button(self): + if len(self.model_view.get_scan_list()) > 1: + self.disable_render_windows_update = True + self.model_view.remove_scan(None) + self.update_all_scan_combo_boxes() + self.disable_render_windows_update = False + self.update_render_windows() + self.activate_delete_button() + + def handle_feature_clear_image_button(self): + feature_class_index = self.ui.listWidget_feature_classes.currentRow() + image_number = self.ui.spinBox_window_1.value() + if feature_class_index < 0: + feature_class_index = 0 + self.model_view.remove_all_feature_points_from_image(feature_class_index, image_number=image_number) + self.update_render_windows() + self.update_feature_class_list_widget() + + def handle_reset_horizontal_layout(self): + self.ui.splitter_render_window_1.setStretchFactor(1, 1) + self.ui.splitter_render_window_2.setStretchFactor(1, 1) + self.ui.splitter_render_windows.setSizes([1000000, 1000000]) + self.ui.splitter_central_widget.setSizes([1000000, 1000000]) + + def handle_saasmi_button(self): + image_number = self.ui.spinBox_window_1.value() + beta = self.ui.doubleSpinBox_saasmi_beta.value() + feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if feature_class_index < 0: + feature_class_list = self.model_view.get_feature_classes_list() + if "Saasmi Features" not in feature_class_list: + self.model_view.add_feature_class("Saasmi Features", [0, 0, 255]) + feature_class_list = self.model_view.get_feature_classes_list() + feature_class_index = feature_class_list.index("Saasmi Features") + auto_generate_rag = self.ui.checkBox_auto_generate_rag.isChecked() + self.model_view.set_config_by_key_sub_key("saasmi", "auto_generate_rag", auto_generate_rag) + self.model_view.set_config_by_key_sub_key("saasmi", "beta", beta) + try: + dialog_saasmi = saasmi_dialog.SaasmiDialog(self.model_view, image_number=image_number, beta=beta, + feature_class_index=feature_class_index, + auto_generate_rag=auto_generate_rag) + dialog_saasmi.showMaximized() + dialog_saasmi.exec_() + self.ui.doubleSpinBox_saasmi_disk_size.setValue( + self.model_view.get_config_by_key_sub_key("saasmi", "disk_size")) + except ValueError: + log.exception("message") + + self.update_feature_class_list_widget() + self.ui.listWidget_feature_classes.setCurrentRow(feature_class_index) + self.update_render_windows() + + def handle_feature_points_list_widget_clicked(self): + self.update_render_windows() + + def handle_features_list_widget_clicked(self): + self.ui.listWidget_feature_points.setCurrentRow(-1) + self.update_render_windows() + + def handle_saasmi_number_of_neighbors(self): + number_of_neighbors = self.ui.spinBox_number_of_neighbors.value() + self.model_view.set_config_by_key_sub_key("saasmi", "k-neighbors", number_of_neighbors) + + def handle_saasmi_disk_size(self): + disk_size = self.ui.doubleSpinBox_saasmi_disk_size.value() + self.model_view.set_config_by_key_sub_key("saasmi", "disk_size", disk_size) + + def handle_ransac_button(self): + reference_image_number = self.ui.spinBox_ransac_reference_image_number.value() + image_class_name = self.model_view.drift_correction_by_ransac(reference_image_number=reference_image_number) + self.update_scan_image_class_combo_box() + self.ui.comboBox_scan_image_class.setCurrentText(image_class_name) + + def update_step_size_fields(self): + step_size = self.model_view.get_step_size() + self.ui.lineEdit_step_size_x.setText(str(step_size[0])) + self.ui.lineEdit_step_size_y.setText(str(step_size[1])) + + def handle_step_size_update_button(self): + formula_x = self.ui.lineEdit_step_size_x.text() + formula_y = self.ui.lineEdit_step_size_y.text() + + self.model_view.set_step_size_by_formula(formula_x=formula_x, formula_y=formula_y) + self.update_render_windows() + + def update_scan_information(self): + current_image_number = self.ui.spinBox_window_1.value() + scan_information_string = self.model_view.get_scan_information() + scan_image_information = self.model_view.get_scan_image_information(current_image_number) + self.ui.textBrowser_scan_information.setText(scan_information_string + "\n\n" + scan_image_information) + + def update_scan_image_class_combo_box(self): + index = self.ui.comboBox_scan_image_class.currentIndex() + prev_render_window_disabled = self.disable_render_windows_update + self.disable_render_windows_update = True + self.ui.comboBox_scan_image_class.clear() + image_classes = self.model_view.get_image_classes() + self.ui.comboBox_scan_image_class.addItems(image_classes) + item_count = self.ui.comboBox_scan_image_class.count() + self.disable_render_windows_update = prev_render_window_disabled + if index < item_count: + self.ui.comboBox_scan_image_class.setCurrentIndex(index) + else: + self.ui.comboBox_scan_image_class.setCurrentIndex(item_count - 1) + + def handle_delete_image_class_button(self): + item_count = self.ui.comboBox_scan_image_class.count() + if item_count == 1: + return + image_class = self.ui.comboBox_scan_image_class.currentText() + index = self.ui.comboBox_scan_image_class.currentIndex() + self.model_view.remove_image_class_from_scan(image_class) + self.update_scan_image_class_combo_box() + + def handle_show_rag_checkbox(self): + self.update_render_windows() + self.update_feature_class_list_widget() + + def handle_show_feature_checkbox(self): + self.update_render_windows() + self.update_feature_class_list_widget() + + + def handle_export_lines_as_csv_button(self): + self.model_view.export_lines_as_csv() + + def save_default_feature_settings(self): + feature_detection_parameters = {"min_sigma": self.ui.doubleSpinBox_feature_auto_detect_min_sigma.value(), + "max_sigma": self.ui.doubleSpinBox_feature_auto_detect_max_sigma.value(), + "num_sigma": self.ui.spinBox_feature_auto_detect_num_sigma.value(), + "threshold": self.ui.doubleSpinBox_feature_auto_detect_threshold.value(), + "overlap": self.ui.doubleSpinBox_feature_auto_detect_overlap.value(), + "detect_maxima": self.ui.checkBox_feature_auto_detect_detect_minima.isChecked(), } + self.set_feature_settings_config_by_key("feature_detector_parameters", feature_detection_parameters) + self.set_feature_settings_config_by_key('feature_point_size', + self.ui.doubleSpinBox_feature_point_size.value()) + self.set_feature_settings_config_by_key('feature_geometry', + self.ui.comboBox_feature_representation.currentText()) + self.set_feature_settings_config_by_key("auto_detect_feature_distance", + self.ui.doubleSpinBox_feature_distance.value()) + self.set_feature_settings_config_by_key('feature_thickness_percent', + self.ui.doubleSpinBox_feature_thickness.value()) + self.set_feature_settings_config_by_key('highlight_method', + self.ui.comboBox_feature_feature_highlight_method.currentText()) + self.set_feature_settings_config_by_key('highlight_brightness', + self.ui.doubleSpinBox_feature_highlight_brightness.value()) + feature_highlight_color = [self.ui.spinBox_feature_highlight_color_r.value(), + self.ui.spinBox_feature_highlight_color_g.value(), + self.ui.spinBox_feature_highlight_color_b.value(), + 255] + self.set_feature_settings_config_by_key('highlight_color', feature_highlight_color) + self.set_feature_settings_config_by_key('feature_detector', + self.ui.comboBox_feature_auto_detect_function.currentText()) + + def load_default_feature_settings(self): + feature_detection_parameters = self.get_feature_settings_config_by_key("feature_detector_parameters") + self.ui.doubleSpinBox_feature_auto_detect_min_sigma.setValue(feature_detection_parameters["min_sigma"]) + self.ui.doubleSpinBox_feature_auto_detect_max_sigma.setValue(feature_detection_parameters["max_sigma"]) + self.ui.spinBox_feature_auto_detect_num_sigma.setValue(feature_detection_parameters["num_sigma"]) + self.ui.doubleSpinBox_feature_auto_detect_threshold.setValue(feature_detection_parameters["threshold"]) + self.ui.doubleSpinBox_feature_auto_detect_overlap.setValue(feature_detection_parameters["overlap"]) + self.ui.checkBox_feature_auto_detect_detect_minima.setChecked(feature_detection_parameters["detect_maxima"]) + self.ui.comboBox_feature_auto_detect_function.clear() + self.ui.comboBox_feature_auto_detect_function.addItems( + self.model_view.get_feature_detection_function_list()) + try: + self.ui.comboBox_feature_auto_detect_function.setCurrentText( + self.get_feature_settings_config_by_key('feature_detector') + ) + except TypeError: + self.ui.comboBox_feature_auto_detect_function.setCurrentIndex(0) + self.ui.doubleSpinBox_feature_point_size.setValue( + self.get_feature_settings_config_by_key("feature_point_size")) + self.ui.comboBox_feature_representation.setCurrentText( + self.get_feature_settings_config_by_key('feature_geometry')) + self.ui.doubleSpinBox_feature_distance.setValue( + self.get_feature_settings_config_by_key("auto_detect_feature_distance")) + self.ui.doubleSpinBox_feature_thickness.setValue( + self.get_feature_settings_config_by_key("feature_thickness_percent")) + self.ui.comboBox_feature_feature_highlight_method.addItems( + feature_visualization_vtk.HIGHLIGHT_COLOR_MODI) + self.ui.comboBox_feature_feature_highlight_method.setCurrentText( + self.get_feature_settings_config_by_key("highlight_method")) + self.ui.doubleSpinBox_feature_highlight_brightness.setValue( + self.get_feature_settings_config_by_key("highlight_brightness")) + fixed_color = self.get_feature_settings_config_by_key("highlight_color") + self.ui.spinBox_feature_highlight_color_r.setValue(fixed_color[0]) + self.ui.spinBox_feature_highlight_color_g.setValue(fixed_color[1]) + self.ui.spinBox_feature_highlight_color_b.setValue(fixed_color[2]) + + def apply_feature_controller_settings(self): + self.save_default_feature_settings() + self.update_render_windows() + + def get_feature_settings_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('feature_settings', key) + + def set_feature_settings_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('feature_settings', key, value) + + def handle_select_feature_check_box_clicked(self): + self.generate_selected_feature_list() + + def closeEvent(self, event): + self.main_render_window.render_window.Finalize() + self.second_render_window.render_window.Finalize() + event.accept() + + def handle_auto_save_checkbox(self): + self.model_view.set_config_by_key_sub_key('program_settings', 'auto_save_interval', + self.ui.spinBox_autosave_in_seconds.value()) + self.model_view.set_config_by_key_sub_key('program_settings', 'auto_save', + self.ui.checkBox_enable_autosave.isChecked()) + self.timer = QtCore.QTimer() + if self.ui.checkBox_enable_autosave.isChecked(): + self.timer.setInterval(self.ui.spinBox_autosave_in_seconds.value() * 1000) + self.timer.timeout.connect(self.model_view.auto_save_function) + self.timer.start() + else: + self.timer.stop() + + def handle_show_measuring_points_dialog(self): + current_image_number = self.ui.spinBox_window_1.value() + try: + self.model_view.get_1d_signal_by_number(current_image_number) + has_1d_data = True + except AttributeError: + has_1d_data = False + if has_1d_data: + dialog = measuring_points_dialog.MeasuringPointsDialog(model_view=self.model_view, + image_number=current_image_number) + dialog.showMaximized() + dialog.exec_() + else: + QtWidgets.QMessageBox.information(self, "Information", "This Scan does not have measuring points") + + def handle_show_density_dialog(self): + current_image_number = self.ui.spinBox_window_1.value() + try: + density_data = self.model_view.get_density_array_by_image_number(current_image_number) + except: + density_data = None + if density_data is not None: + dialog = density_dialog.DensityDialog(model_view=self.model_view, + image_number=current_image_number) + dialog.showMaximized() + dialog.exec_() + else: + QtWidgets.QMessageBox.information(self, "Information", + "This Scan does not have measuring point density data") + + def handle_open_rescale_feature_dialog(self): + dialog = rescale_feature_dialog.RescaleFeatureDialog(model_view=self.model_view, parent=self) + if dialog.exec_(): + self.update_render_windows() + + def handle_open_line_profile_dialog(self): + current_image_number = self.ui.spinBox_window_1.value() + self.dialog_line_profile = line_profile_dialog.LineProfileDialog(model_view=self.model_view, + image_number=current_image_number) + self.dialog_line_profile.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) + self.dialog_line_profile.showMaximized() + if self.dialog_line_profile.exec_(): + self.update_render_windows() + + def handle_show_saasmi_prepared_images(self): + current_image_number = self.ui.spinBox_window_1.value() + disk_size = self.ui.doubleSpinBox_saasmi_disk_size.value() + im_denoised, im_adapthist, im_eq = self.model_view.get_saasmi_prepared_images(current_image_number, + disk_size) + saasmi_prepared_images.show_prepared_images(im_denoised, im_adapthist, im_eq) + + def add_left_click_menu_to_feature_classes_list_widget(self): + self.ui.listWidget_feature_classes.installEventFilter(self) + text_call_back_dict = {"Rename": self.feature_class_right_click_menu_change_name, + "Change Visualization": self.feature_class_right_click_menu_change_visualization} + self.right_click_menu = right_click_menu.ListWidgetRightClickMenu(text_call_back_dict) + + def feature_class_right_click_menu_change_name(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_class_name = self.model_view.get_feature_classes_list()[current_feature_class_index] + dialog_rename_feature_class = rename_dialog.RenameDialog(current_feature_class_name, self.model_view, + parent=self) + if dialog_rename_feature_class.exec_(): + self.update_feature_class_list_widget() + + def feature_class_right_click_menu_change_visualization(self): + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + current_feature_class_name = self.model_view.get_feature_classes_list()[current_feature_class_index] + dialog = feature_class_visualization_dialog.FeatureClassVisualizationDialog(current_feature_class_name, + self.model_view, self) + if dialog.exec_(): + self.update_render_windows() + + def eventFilter(self, source, event) -> bool: + if (event.type() == QtCore.QEvent.ContextMenu and + source is self.ui.listWidget_feature_classes): + current_item = source.itemAt(event.pos()) + current_element_row = self.ui.listWidget_feature_classes.row(current_item) + try: + current_element_text = current_item.text() + except AttributeError: + current_element_text = None + if current_element_row >= 0: + self.ui.listWidget_feature_classes.setCurrentRow(current_element_row) + self.right_click_menu.current_row_index = current_element_row + self.right_click_menu.current_row_text = current_element_text + self.right_click_menu.exec_(event.globalPos()) + return True + return super().eventFilter(source, event) + + def handle_open_jump_visualization_dialog(self): + dialog = jump_visualization_dialog.JumpVisualizationDialog(self.model_view) + dialog.exec_() + + def handle_show_average_image(self): + current_image_number = self.ui.spinBox_window_1.value() + dialog = average_image_dialog.AverageImageDialog(self.model_view, image_number=current_image_number) + dialog.exec_() + + def feature_check_box_check_all(self, feature_class_name, feature_class_index): + feature_list = self.model_view.get_features_by_feature_class_index( + feature_class_index=feature_class_index) + self.selected_feature_dict[feature_class_name] = feature_list + self.update_feature_class_list_widget() + + def feature_check_box_uncheck_all(self, feature_class_name, feature_class_index): + self.selected_feature_dict[feature_class_name] = [] + self.update_feature_class_list_widget() + + def handle_feature_class_check_all(self): + feature_classes = self.model_view.get_feature_classes_list() + for feature_class_index, feature_class_name in enumerate(feature_classes): + self.feature_check_box_check_all(feature_class_name, feature_class_index) + + def handle_feature_class_uncheck_all(self): + feature_classes = self.model_view.get_feature_classes_list() + for feature_class_index, feature_class_name in enumerate(feature_classes): + self.feature_check_box_uncheck_all(feature_class_name, feature_class_index) + + def handle_feature_index_check_all(self): + current_feature_class_item = self.ui.listWidget_feature_classes.currentItem() + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if current_feature_class_item is None: + return + else: + feature_class_name = current_feature_class_item.text() + self.feature_check_box_check_all(feature_class_name, current_feature_class_index) + + def handle_feature_index_uncheck_all(self): + current_feature_class_item = self.ui.listWidget_feature_classes.currentItem() + current_feature_class_index = self.ui.listWidget_feature_classes.currentRow() + if current_feature_class_item is None: + return + else: + feature_class_name = current_feature_class_item.text() + self.feature_check_box_uncheck_all(feature_class_name, current_feature_class_index) + + def handle_feature_rag_dialog(self): + image_number = self.ui.spinBox_window_1.value() + self.model_view.generate_feature_rags() + self.dialog = feature_rag_dialog.FeatureRagDialog(self.model_view, image_number) + self.dialog.showMaximized() + self.dialog.exec_() + + def handle_contrast_dialog(self): + image_number = self.ui.spinBox_window_1.value() + self.contrast_dialog = contrast_dialog.ContrastDialog(model_view=self.model_view, image_number=image_number) + self.contrast_dialog.exec_() + self.load_visualization_settings() + + def handle_generate_feature_grid(self): + dialog = feature_grid_dialog.FeatureGridDialog(model_view=self.model_view) + dialog.exec_() + + def handle_feature_grid_check_box(self): + self.update_render_windows() + + def handle_show_feature_grid_check_box(self): + self.update_render_windows() + + def handle_apply_feature_grid_to_features(self): + self.model_view.apply_feature_grid_to_features() + + def dragEnterEvent(self, event): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + def handle_export_training_data(self): + dialog = dialog_neural_networks.DialogNeuralNetworks(model_view=self.model_view) + dialog.exec_() + # options = QtWidgets.QFileDialog.Options() + # options |= QtWidgets.QFileDialog.DontUseNativeDialog + # last_file_path = os.getcwd() + # file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', last_file_path, "Training Data(*.zip)", + # options=options) + # if file_path != "": + # self.model_view.generate_training_data(file_path) + + def dropEvent(self, event): + files = [u.toLocalFile() for u in event.mimeData().urls()] + for file_path in files: + self.accept_files_per_drag_and_drop(file_path=file_path) + + def accept_files_per_drag_and_drop(self, file_path): + file_name_extension = file_path.split(".")[-1] + plain_import_file_formats = ["bmp", "tif", "png", "jpg", "jpeg", 'avi', 'zip', 'stp', 'vsy', 'sxm', 'arn'] + if file_name_extension == "h5": + self.open_import_h5_dialog(file_path=file_path) + elif file_name_extension == "gwy": + self.handle_import_gwy(file_path=file_path) + elif file_name_extension.lower() in plain_import_file_formats: + self.model_view.load_file(file_path) + self.scan_loaded() + else: + QtWidgets.QMessageBox.information(self, "Wrong file format", + "The file format of the file {} is not supported".format(file_path)) + + def handle_plot_h5_info(self): + has_plotable_dict = self.model_view.check_plot_dict() + if not has_plotable_dict: + log.info("The scan has no plot data, chose the source h5 file to add the plots to the scan file") + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Choose source H5 File', os.getcwd(), + "H5-File(*.h5)", + options=options) + if file_path != "": + has_plotable_dict = self.model_view.generate_plotable_dict_from_h5_file(file_path) + if has_plotable_dict: + dialog_ = plotable_dialog.PlotableDialog(model_view=self.model_view) + dialog_.exec_() + else: + log.info("No plot data found") + + def handle_show_point_cloud_dialog(self): + image_number = self.ui.spinBox_window_1.value() + dialog = point_cloud_dialog.PointCloudDialog(model_view=self.model_view, image_number=image_number) + dialog.exec_() diff --git a/view/matplotlib_chart/__init__.py b/view/matplotlib_chart/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/matplotlib_chart/__init__.py @@ -0,0 +1 @@ + diff --git a/view/matplotlib_chart/chart.py b/view/matplotlib_chart/chart.py new file mode 100644 index 0000000..ba33f20 --- /dev/null +++ b/view/matplotlib_chart/chart.py @@ -0,0 +1,60 @@ + + +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib.backends import backend_qt5agg +from matplotlib.path import Path +from matplotlib.pyplot import Figure +from matplotlib.widgets import LassoSelector + + +class CustomQtChart(QtCore.QObject): + selected_points_signal = QtCore.pyqtSignal(list) + + def __init__(self, widget, lasso_selector=False): + QtCore.QObject.__init__(self) + self.figure = Figure() + self.axis = self.figure.add_subplot(111) + + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.vl.addWidget(self.canvas) + widget.setLayout(self.vl) + if lasso_selector: + self.lasso_selector = LassoSelector(self.axis, onselect=self.onselect) + self.figure.tight_layout() + + def update(self): + self.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, data, title_x_axis="", title_y_axis=""): + self.axis.clear() + self.axis.plot(data) + self.update() + + def reset_render_window(self): + self.update() + + def onselect(self, verts): + path = Path(verts) + self.ind = np.nonzero(path.contains_points(self.xys))[0] + points = self.xys[self.ind] + self.axis.scatter(points[:, 0], points[:, 1], color=[0, 0, 0], marker="x") + self.selected_points_signal.emit(np.ndarray.tolist(points)) + self.canvas.draw_idle() + + def disconnect(self): + self.lasso.disconnect_events() + self.fc[:, -1] = 1 + self.collection.set_facecolors(self.fc) + self.canvas.draw_idle() diff --git a/view/measuring_points_dialog.py b/view/measuring_points_dialog.py new file mode 100644 index 0000000..47648db --- /dev/null +++ b/view/measuring_points_dialog.py @@ -0,0 +1,371 @@ +import logging +import pprint + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from PyQt5 import QtWidgets, QtCore +from scipy.optimize import curve_fit + +from model_view import frontend_adapter +from view.measuring_points_visualization.matplotlib.measuring_points_render_window import MeasuringPointsRenderWindow, \ + MeasuringPointsAnimation +from view.string_formatting.math_text import sci_notation +from view.ui import ui_dialog_measuring_points + +log = logging.getLogger(__name__) + + +class MeasuringPointsDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_measuring_points.Ui_Dialog() + self.measuring_point_diagram_information = None + self.measuring_points = None + self.ui_dialog.setupUi(self) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.set_up_ui_fields() + self.render_window = MeasuringPointsRenderWindow(widget=self.ui_dialog.widget_render_window, + model_view=self.model_view) + self.render_window.lasso_selection_signal.connect(self.handle_lasso_selection) + self.ui_dialog.checkBox_show_image.stateChanged.connect(self.update_render_window) + self.ui_dialog.radioButton_show_measuring_points.toggled.connect(self.update_render_window) + self.ui_dialog.radioButton_show_measuring_points_velocity.toggled.connect(self.update_render_window) + self.ui_dialog.checkBox_show_ideal_measuring_points.stateChanged.connect(self.update_render_window) + self.ui_dialog.comboBox_real_measuring_points_plot_type.currentTextChanged.connect(self.update_render_window) + self.ui_dialog.comboBox_ideal_measuring_points_plot_type.currentTextChanged.connect(self.update_render_window) + self.ui_dialog.doubleSpinBox_ideal_measuring_points_size.valueChanged.connect(self.update_render_window) + self.ui_dialog.doubleSpinBox_real_measuring_points_size.valueChanged.connect(self.update_render_window) + self.ui_dialog.pushButton_real_measuring_points_color.clicked.connect(self.handle_real_points_color_button) + self.ui_dialog.pushButton_ideal_measuring_points_color.clicked.connect(self.handle_ideal_points_color_button) + self.ui_dialog.comboBox_image_color_map.currentTextChanged.connect(self.handle_image_color_map_changed) + self.ui_dialog.pushButton_image_nan_color.clicked.connect(self.handle_image_nan_color_button) + self.ui_dialog.spinBox_image_number.valueChanged.connect(self.handle_image_number_changed) + self.ui_dialog.pushButton_show_time_diagram.setEnabled(False) + self.ui_dialog.pushButton_show_time_diagram.clicked.connect(self.handle_show_time_diagram) + self.ui_dialog.pushButton_show_velocity_diagram.clicked.connect(self.handle_show_velocity_diagram) + self.ui_dialog.pushButton_real_point_animate.clicked.connect(self.handle_real_points_animation_button) + self.ui_dialog.pushButton_ideal_points_show_velocity_diagram.clicked.connect( + self.handle_show_ideal_velocity_diagram) + self.ui_dialog.pushButton_select_all.clicked.connect(self.handle_select_all_button) + self.ui_dialog.pushButton_plot_angular_velocity.clicked.connect(self.handle_show_angular_velocity_diagram) + self.ui_dialog.pushButton_plot_ideal_angular_velocity.clicked.connect( + self.handle_show_ideal_angular_velocity_diagram) + self.ui_dialog.radioButton_angular_velocity_middle_point.clicked.connect( + self.handle_middle_point_radio_button_changed) + self.ui_dialog.radioButton_angular_velocity_point_index.clicked.connect( + self.handle_middle_point_radio_button_changed) + self.ui_dialog.spinBox_angular_velocity_point_index.editingFinished.connect( + self.handle_middle_point_radio_button_changed) + self.update_render_window() + + def set_up_ui_fields(self): + self._set_up_image_ui() + self._set_up_real_points_ui() + self._set_up_ideal_points_ui() + self._set_up_image_spin_box() + + def _set_up_image_spin_box(self): + max_image_number = self.model_view.get_maximum_image_index() + self.ui_dialog.spinBox_image_number.setMaximum(max_image_number) + self.set_middle_point_index_min_max() + self.ui_dialog.spinBox_image_number.setValue(self.image_number) + + def _set_up_image_ui(self): + color_maps = matplotlib.pyplot.colormaps() + self.ui_dialog.comboBox_image_color_map.addItems(color_maps) + show_image = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", "image_show") + image_color_map = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", "image_color_map") + self.image_nan_color = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", "image_nan_color") + self.ui_dialog.checkBox_show_image.setChecked(show_image) + self.ui_dialog.comboBox_image_color_map.setCurrentText(image_color_map) + self._set_button_color(self.ui_dialog.pushButton_image_nan_color, self.image_nan_color) + + def _set_up_real_points_ui(self): + self.ui_dialog.comboBox_real_measuring_points_plot_type.addItems(["Line", "Scatter"]) + show_real_points = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", "real_points_show") + real_points_line_width = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "real_points_size") + self.real_points_color = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "real_points_color") + real_points_plot_type = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "real_points_plot_type") + self.ui_dialog.radioButton_show_measuring_points.setChecked(show_real_points) + self.ui_dialog.doubleSpinBox_real_measuring_points_size.setValue(real_points_line_width) + self.ui_dialog.comboBox_real_measuring_points_plot_type.setCurrentText(real_points_plot_type) + self._set_button_color(self.ui_dialog.pushButton_real_measuring_points_color, color=self.real_points_color) + + def _set_up_ideal_points_ui(self): + self.ui_dialog.comboBox_ideal_measuring_points_plot_type.addItems(["Line", "Scatter"]) + show_ideal_points = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", "ideal_points_show") + ideal_points_line_width = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_size") + self.ideal_points_color = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_color") + ideal_points_plot_type = self.model_view.get_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_plot_type") + + self.ui_dialog.checkBox_show_ideal_measuring_points.setChecked(show_ideal_points) + self.ui_dialog.doubleSpinBox_ideal_measuring_points_size.setValue(ideal_points_line_width) + self._set_button_color(self.ui_dialog.pushButton_ideal_measuring_points_color, color=self.ideal_points_color) + self.ui_dialog.comboBox_ideal_measuring_points_plot_type.setCurrentText(ideal_points_plot_type) + + def _get_color_from_color_dialog(self): + color_dialog = QtWidgets.QColorDialog() + color = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog).getRgb() + color = color[:3] + color = [c / 255 for c in color] + return color + + def handle_image_nan_color_button(self): + self.image_nan_color = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_image_nan_color, self.image_nan_color) + self.update_render_window() + + def handle_real_points_color_button(self): + self.real_points_color = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_real_measuring_points_color, self.real_points_color) + self.update_render_window() + + def handle_ideal_points_color_button(self): + self.ideal_points_color = self._get_color_from_color_dialog() + self._set_button_color(self.ui_dialog.pushButton_ideal_measuring_points_color, self.ideal_points_color) + self.update_render_window() + + def _set_button_color(self, button, color): + button.setStyleSheet( + "background-color : rgb({r},{g},{b})".format(r=color[0] * 255, g=color[1] * 255, b=color[2] * 255)) + + def get_visualization_settings(self): + visualization_settings = {"show_image": self.ui_dialog.checkBox_show_image.isChecked(), + "image_settings": { + "nan_color": self.image_nan_color, + "color_map": self.ui_dialog.comboBox_image_color_map.currentText(), + }, + "real_points_settings": { + "show_measuring_points": self.ui_dialog.radioButton_show_measuring_points.isChecked(), + "show_measuring_points_velocity": self.ui_dialog.radioButton_show_measuring_points_velocity.isChecked(), + "points_plot_type": self.ui_dialog.comboBox_real_measuring_points_plot_type.currentText(), + "points_size": self.ui_dialog.doubleSpinBox_real_measuring_points_size.value(), + "points_color": self.real_points_color, + "middle_point": self.ui_dialog.radioButton_angular_velocity_middle_point.isChecked(), + "middle_point_index": self.ui_dialog.spinBox_angular_velocity_point_index.value(), + }, + "ideal_points_settings": { + "show_ideal_measuring_points": self.ui_dialog.checkBox_show_ideal_measuring_points.isChecked(), + "points_plot_type": self.ui_dialog.comboBox_ideal_measuring_points_plot_type.currentText(), + "points_size": self.ui_dialog.doubleSpinBox_ideal_measuring_points_size.value(), + "points_color": self.ideal_points_color, }, + } + return visualization_settings + + def update_render_window(self): + self.write_ui_to_config() + visualization_settings = self.get_visualization_settings() + image_number = self.ui_dialog.spinBox_image_number.value() + self.render_window.render(image_number=image_number, visualization_settings=visualization_settings) + + def write_ui_to_config(self): + show_image = self.ui_dialog.checkBox_show_image.isChecked() + image_color_map = self.ui_dialog.comboBox_image_color_map.currentText() + image_nan_color = self.image_nan_color + show_real_points = self.ui_dialog.radioButton_show_measuring_points.isChecked() + show_ideal_points = self.ui_dialog.checkBox_show_ideal_measuring_points.isChecked() + real_points_plot_type = self.ui_dialog.comboBox_real_measuring_points_plot_type.currentText() + ideal_points_plot_type = self.ui_dialog.comboBox_ideal_measuring_points_plot_type.currentText() + real_points_size = self.ui_dialog.doubleSpinBox_real_measuring_points_size.value() + ideal_points_size = self.ui_dialog.doubleSpinBox_ideal_measuring_points_size.value() + real_points_color = self.real_points_color + ideal_points_color = self.ideal_points_color + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", "image_show", show_image) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", "image_color_map", image_color_map) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", "image_nan_color", image_nan_color) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", "real_points_show", show_real_points) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", "ideal_points_show", show_ideal_points) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "real_points_plot_type", real_points_plot_type) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_plot_type", ideal_points_plot_type) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "real_points_size", real_points_size) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "real_points_color", real_points_color) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_size", ideal_points_size) + self.model_view.set_config_by_key_sub_key("measuring_points_dialog", + "ideal_points_color", ideal_points_color) + + def handle_image_number_changed(self): + self.image_number = self.ui_dialog.spinBox_image_number.value() + self.ui_dialog.spinBox_image_number.setEnabled(False) + QtWidgets.QApplication.processEvents() + self.render_window.real_points = None + self.render_window.selector.select_all() + self.set_middle_point_index_min_max() + self.update_render_window() + self.ui_dialog.spinBox_image_number.setEnabled(True) + + def set_middle_point_index_min_max(self): + image_1d = self.model_view.get_1d_signal_by_number(self.ui_dialog.spinBox_image_number.value()) + self.ui_dialog.spinBox_angular_velocity_point_index.setMinimum(-1) + self.ui_dialog.spinBox_angular_velocity_point_index.setMaximum(len(image_1d) - 1) + + def handle_lasso_selection(self, measuring_points): + self.measuring_points = measuring_points + self.update_measuring_point_information() + self.ui_dialog.pushButton_show_time_diagram.setEnabled(True) + + def update_measuring_point_information(self): + image_number = self.image_number + middle_point = self.ui_dialog.radioButton_angular_velocity_middle_point.isChecked() + point_index = self.ui_dialog.spinBox_angular_velocity_point_index.value() + information_dict = self.model_view.get_measuring_point_informations(image_number=image_number, + measuring_points=self.measuring_points, + middle_point=middle_point, + point_index=point_index) + self.measuring_point_diagram_information = information_dict.pop("time_information") + self._add_dict_to_text_browser(information_dict, self.ui_dialog.textBrowser_selection_information) + + def handle_select_all_button(self): + try: + self.render_window.selector.select_all() + except AttributeError: + log.info("Real images have to be drawn") + + def _add_dict_to_text_browser(self, input_dict, text_browser_widget): + text_browser_widget.clear() + text = "" + for key, value in input_dict.items(): + text += "{}: {} \n".format(key, pprint.pformat(value)) + text_browser_widget.setText(text) + + def handle_image_color_map_changed(self): + self.update_render_window() + + def handle_real_points_animation_button(self): + image_number = self.ui_dialog.spinBox_image_number.value() + visualization_settings = self.get_visualization_settings() + self.measuring_animation = MeasuringPointsAnimation(self.model_view) + animation_skip_points = self.ui_dialog.spinBox_points_to_skip.value() + self.measuring_animation.start_animation(image_number=image_number, + visualization_settings=visualization_settings, + animation_skip_points=animation_skip_points) + + def handle_show_time_diagram(self): + if self.measuring_point_diagram_information is None: + self.render_window.selector.select_all() + plot_information = self.measuring_point_diagram_information["time_stamp_value_array"] + start_time = self.measuring_point_diagram_information["image_start_time"] + end_time = self.measuring_point_diagram_information["image_end_time"] + plt.ion() + fig, ax = plt.subplots() + ax.set(xlabel='time (s)') + if start_time is not None and end_time is not None: + ax.set_xlim([start_time, end_time]) + ax.scatter(plot_information[:, 0], plot_information[:, 1]) + ax.grid() + plt.show() + + def handle_show_velocity_diagram(self): + if self.measuring_point_diagram_information is not None: + plot_information = self.measuring_point_diagram_information["velocity_array"] + start_time = self.measuring_point_diagram_information["image_start_time"] + end_time = self.measuring_point_diagram_information["image_end_time"] + plt.ion() + fig, ax = plt.subplots() + ax.set(xlabel='time (s)') + if start_time is not None and end_time is not None: + ax.set_xlim([start_time, end_time]) + ax.scatter(plot_information[:, 0], plot_information[:, 1]) + # plot trend line + z = np.polyfit(plot_information[:, 0], plot_information[:, 1], 1) + p = np.poly1d(z) + plt.plot(plot_information[:, 0], p(plot_information[:, 0]), "r--") + # __ + ax.grid() + plt.show() + else: + QtWidgets.QMessageBox.about(self, "Information", "No points selected, select points to plot the graph") + + def handle_middle_point_radio_button_changed(self): + self.update_measuring_point_information() + self.update_render_window() + + def handle_show_ideal_velocity_diagram(self): + image_number = self.ui_dialog.spinBox_image_number.value() + ideal_points_velocity = self.model_view.get_velocity_array(image_number, ideal=True) + if ideal_points_velocity is None: + log.info("File has no ideal points") + return + time_stamp_delta = self.model_view.get_time_stamp_delta(image_number) / len(ideal_points_velocity) + time_array = np.arange(ideal_points_velocity.shape[0]) * time_stamp_delta + plt.ion() + fig, ax = plt.subplots() + ax.scatter(time_array, ideal_points_velocity) + # plot trend line + z = np.polyfit(time_array, ideal_points_velocity, 1) + p = np.poly1d(z) + plt.plot(time_array, p(time_array), "r--") + # __ + ax.grid() + plt.show() + + def handle_show_ideal_angular_velocity_diagram(self): + def func(x, a, b): + return a * 1 / np.sqrt(x) + b + + image_number = self.ui_dialog.spinBox_image_number.value() + middle_point = self.ui_dialog.radioButton_angular_velocity_middle_point.isChecked() + point_index = self.ui_dialog.spinBox_angular_velocity_point_index.value() + ideal_angular_velocity = self.model_view.get_angular_velocity_array(image_number, middle_point, point_index, + ideal=True) + if ideal_angular_velocity is None: + log.info("File has no ideal points") + return + time_stamp_delta = self.model_view.get_time_stamp_delta(image_number) / len(ideal_angular_velocity) + time_array = np.arange(ideal_angular_velocity.shape[0]) * time_stamp_delta + plt.ion() + fig, ax = plt.subplots() + ax.scatter(time_array, ideal_angular_velocity) + # plot trend line + popt, pcov = curve_fit(func, time_array[1:], ideal_angular_velocity[1:]) + plt.title("Fitting Function: " + r'${}\left(\frac{{1}}{{\sqrt{{x}}}}\right){}$'.format(sci_notation(popt[0]), + sci_notation(popt[1], + force_sign=True))) + # plt.title("The fitting function is: {} * 1 / sqrt(x) + {}".format(popt[0], popt[1])) + plt.plot(time_array, func(time_array, *popt), "r--") + # __ + ax.grid() + plt.show() + + def handle_show_angular_velocity_diagram(self): + def func(x, a, b): + return a * 1 / np.sqrt(x) + b + + if self.measuring_point_diagram_information is not None: + plot_information = self.measuring_point_diagram_information["angular_velocity_array"] + start_time = self.measuring_point_diagram_information["image_start_time"] + end_time = self.measuring_point_diagram_information["image_end_time"] + plt.ion() + fig, ax = plt.subplots() + ax.set(xlabel='time (s)') + if start_time is not None and end_time is not None: + ax.set_xlim([start_time, end_time]) + ax.scatter(plot_information[:, 0], plot_information[:, 1]) + # plot trend line + popt, pcov = curve_fit(func, plot_information[1:, 0], plot_information[1:, 1]) + plt.title( + "Fitting Function: " + r'${}\left(\frac{{1}}{{\sqrt{{x}}}}\right){}$'.format(sci_notation(popt[0]), + sci_notation(popt[1], + force_sign=True))) + plt.plot(plot_information[:, 0], func(plot_information[:, 0], *popt), "r--") + # _____ + ax.grid() + plt.show() + else: + QtWidgets.QMessageBox.about(self, "Information", "No points selected, select points to plot the graph") + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/measuring_points_visualization/__init__.py b/view/measuring_points_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/measuring_points_visualization/matplotlib/__init__.py b/view/measuring_points_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/measuring_points_visualization/matplotlib/measuring_points_render_window.py b/view/measuring_points_visualization/matplotlib/measuring_points_render_window.py new file mode 100644 index 0000000..f5432e8 --- /dev/null +++ b/view/measuring_points_visualization/matplotlib/measuring_points_render_window.py @@ -0,0 +1,302 @@ +import copy + +import matplotlib.colors +import matplotlib.pyplot as plt +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib import cm +from matplotlib.animation import FuncAnimation +from matplotlib.backends import backend_qt5agg +from matplotlib.path import Path +from matplotlib.pyplot import Figure +from matplotlib.widgets import LassoSelector + +from model_view import frontend_adapter + +matplotlib.rcParams['savefig.transparent'] = True + +class SelectFromCollection(object): + """Select indices from a matplotlib collection using `LassoSelector`. + + Selected indices are saved in the `ind` attribute. This tool fades out the + points that are not part of the selection (i.e., reduces their alpha + values). If your collection has alpha < 1, this tool will permanently + alter the alpha values. + + Note that this tool selects collection objects based on their *origins* + (i.e., `offsets`). + + Parameters + ---------- + ax : :class:`~matplotlib.axes.Axes` + Axes to interact with. + + collection : :class:`matplotlib.collections.Collection` subclass + Collection you want to select from. + + alpha_other : 0 <= float <= 1 + To highlight a selection, this tool sets all selected points to an + alpha value of 1 and non-selected points to `alpha_other`. + """ + + def __init__(self, ax, collection, measuring_points, alpha_other=0.3, call_back_function=None): + self.canvas = ax.figure.canvas + self.collection = collection + self.measuring_points = measuring_points + self.alpha_other = alpha_other + self.call_back_function = call_back_function + self.xys = collection.get_offsets() + self.Npts = len(self.xys) + + # Ensure that we have separate colors for each object + self.fc = collection.get_facecolors() + if len(self.fc) == 0: + raise ValueError('Collection must have a facecolor') + elif len(self.fc) == 1: + self.fc = np.tile(self.fc, (self.Npts, 1)) + + self.lasso = LassoSelector(ax, onselect=self.onselect) + self.ind = [] + + def onselect(self, verts): + path = Path(verts) + self.ind = np.nonzero(path.contains_points(self.xys))[0] + self.fc[:, -1] = self.alpha_other + self.fc[self.ind, -1] = 1 + self.collection.set_facecolors(self.fc) + self.canvas.draw_idle() + if len(self.ind) > 0 and self.call_back_function is not None: + self.call_back_function(self.xys[self.ind]) + + def disconnect(self): + self.lasso.disconnect_events() + self.fc[:, -1] = 1 + self.collection.set_facecolors(self.fc) + self.canvas.draw_idle() + + def select_all(self): + self.ind = np.arange(self.xys.shape[0]) + self.fc[:, -1] = self.alpha_other + self.fc[self.ind, -1] = 1 + self.collection.set_facecolors(self.fc) + self.canvas.draw_idle() + if self.call_back_function is not None: + self.call_back_function(self.xys[self.ind]) + + +class MeasuringPointsRenderWindow(QtCore.QObject): + lasso_selection_signal = QtCore.pyqtSignal(object) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + + def update(self): + self.figure.canvas.draw() + + def reset_render_camera(self): + pass + + def render(self, image_number, visualization_settings): + self.figure.clear() + self.real_points = None + self.figure.set_tight_layout(True) + self.axes = self.figure.add_subplot(111) + self.data_range = self.model_view.get_1d_scan_data_range() + if visualization_settings["show_image"]: + self.render_image(image_number, visualization_settings["image_settings"]) + if visualization_settings["real_points_settings"]["show_measuring_points"]: + self.render_measuring_points(image_number, visualization_settings["real_points_settings"]) + if visualization_settings["real_points_settings"]["show_measuring_points_velocity"]: + self.render_measuring_points_velocity(image_number, visualization_settings["real_points_settings"]) + if visualization_settings["ideal_points_settings"]["show_ideal_measuring_points"]: + self.render_ideal_measuring_points(image_number, visualization_settings["ideal_points_settings"]) + if self.real_points is not None: + self.selector = SelectFromCollection(self.axes, self.real_points, measuring_points=self.measuring_points, + call_back_function=self.lasso_selection_call_back_function) + self.set_axis_to_data_range() + self.update() + + def set_axis_to_data_range(self): + self.axes.axis('equal') + self.axes.set_ylim([self.data_range[0][1], self.data_range[0][0]]) + self.axes.set_xlim([self.data_range[1][0], self.data_range[1][1]]) + self.figure.tight_layout() + + def render_image(self, image_number, image_settings): + image = self.model_view.get_2d_image_by_number(image_number) + image = image.astype(float) + contrast_method, contrast_min, contrast_max = self.model_view.get_contrast_settings() + + if contrast_method == 'percentile': + v_min, v_max = np.percentile(image[~np.isnan(image)], (contrast_min, contrast_max)) + normalizer = matplotlib.colors.Normalize(vmin=v_min, vmax=v_max) + elif contrast_method == 'absolute': + normalizer = matplotlib.colors.Normalize(vmin=contrast_min, vmax=contrast_max) + else: + normalizer = None + color_map = copy.copy(cm.get_cmap(image_settings["color_map"])) + color_map.set_bad(color=image_settings["nan_color"]) + + self.axes.imshow(image, zorder=0, cmap=color_map, + extent=[self.data_range[1][0], self.data_range[1][1], self.data_range[0][1], + self.data_range[0][0]], norm=normalizer) + + def render_measuring_points(self, image_number, visualization_settings): + measuring_points = self.model_view.get_1d_signal_by_number(image_number) + measuring_points = self.convert_back_end_points_to_front_end_points(point_array=measuring_points) + color = visualization_settings["points_color"] + size = visualization_settings["points_size"] + if visualization_settings["points_plot_type"] == "Line": + self.axes.plot(measuring_points[:, 1], measuring_points[:, 0], linewidth=size, zorder=5, + color=color) + else: + self.real_points = self.axes.scatter(measuring_points[:, 1], measuring_points[:, 0], s=size, + c=measuring_points[:, 2], + zorder=5) + if visualization_settings["middle_point"]: + angular_velocity_reference_point = self.get_center(measuring_points) + else: + point_index = visualization_settings["middle_point_index"] + angular_velocity_reference_point = measuring_points[point_index, :2] + try: + self.axes.scatter(angular_velocity_reference_point[1], angular_velocity_reference_point[0], s=size * 4, + c=0, + zorder=5) + except IndexError: + print("Index Error") + self.measuring_points = measuring_points + + def render_measuring_points_velocity(self, image_number, visualization_settings): + measuring_points = self.model_view.get_1d_signal_by_number(image_number) + measuring_points = self.convert_back_end_points_to_front_end_points(point_array=measuring_points) + size = visualization_settings["points_size"] + velocity = self.model_view.get_velocity_array(image_number, ideal=False) + self.real_points = self.axes.scatter(measuring_points[:, 1], measuring_points[:, 0], s=size, + c=velocity, + zorder=5) + self.measuring_points = measuring_points + + def render_ideal_measuring_points(self, image_number, visualization_settings): + measuring_points = self.model_view.get_ideal_wave_form(image_number) + real_measuring_points = self.model_view.get_1d_signal_by_number(image_number) + + if measuring_points is None: + return + real_points_data_range = [[real_measuring_points[:, 0].min(), real_measuring_points[:, 0].max()], + [real_measuring_points[:, 1].min(), real_measuring_points[:, 1].max()]] + measuring_points = self.stretch_1d_signal_to_data_range(measuring_points, real_points_data_range) + measuring_points = self.convert_back_end_points_to_front_end_points(point_array=measuring_points) + color = visualization_settings["points_color"] + size = visualization_settings["points_size"] + c = np.array([color]) + if visualization_settings["points_plot_type"] == "Line": + self.axes.plot(measuring_points[:, 1], measuring_points[:, 0], linewidth=size, zorder=6, color=color) + else: + self.axes.scatter(measuring_points[:, 1], measuring_points[:, 0], s=size, c=c, zorder=6) + + def stretch_1d_signal_to_data_range(self, signal_1d, data_range): + signal_1d[:, 0] -= signal_1d[:, 0].min() + signal_1d[:, 0] /= signal_1d[:, 0].max() + signal_1d[:, 0] *= data_range[0][1] - data_range[0][0] + signal_1d[:, 0] += data_range[0][0] + signal_1d[:, 1] -= signal_1d[:, 1].min() + signal_1d[:, 1] /= signal_1d[:, 1].max() + signal_1d[:, 1] *= data_range[1][1] - data_range[1][0] + signal_1d[:, 1] += data_range[1][0] + return signal_1d + + def lasso_selection_call_back_function(self, point_list): + backend_point_list = self.convert_lasso_points_to_back_end_points(point_list) + self.lasso_selection_signal.emit(backend_point_list) + + def reset_render_window(self): + self.update() + + def convert_back_end_points_to_front_end_points(self, point_array): + return point_array + + def convert_lasso_points_to_back_end_points(self, point_array): + point_array[:, [0, 1]] = point_array[:, [1, 0]] + return point_array + + @staticmethod + def get_center(data): + center_x = (np.max(data[:, 0] - np.min(data[:, 0]))) / 2 + np.min(data[:, 0]) + center_y = (np.max(data[:, 1] - np.min(data[:, 1]))) / 2 + np.min(data[:, 1]) + return [center_x, center_y] + + +class MeasuringPointsAnimation(): + def __init__(self, model_view): + self.model_view = model_view + self.figure, self.axes = plt.subplots() + + @staticmethod + def get_arrow(x, y, width=1): + arrow = plt.arrow(x[0], y[0], x[1] - x[0], y[1] - y[0], width=width) + return arrow + + def calc_width(self): + width = abs(np.min(self.animation_array[:, 0]) - np.max(self.animation_array[:, 0])) / 500 + return width + + def animation_update(self, index): + color_map = matplotlib.cm.get_cmap('jet') + color = color_map(index / len(self.animation_array)) + color = [1, 1, 1] + helper_index = index * self.animation_skip_points + width = self.calc_width() + if helper_index > 0: + try: + x_coords = [self.animation_array[helper_index - self.animation_skip_points, 0], + self.animation_array[helper_index, 0]] + y_coords = [self.animation_array[helper_index - self.animation_skip_points, 1], + self.animation_array[helper_index, 1]] + line = self.get_arrow(y_coords, x_coords, + width=width) + self.axes.add_line(line) + except IndexError: + pass + # ax.add_patch(circle) + + def render_image(self, image_number, image_settings): + self.data_range = self.model_view.get_1d_scan_data_range() + image = self.model_view.get_2d_image_by_number(image_number) + image = image.astype(float) + contrast_method, contrast_min, contrast_max = self.model_view.get_contrast_settings() + + if contrast_method == 'percentile': + v_min, v_max = np.percentile(image[~np.isnan(image)], (contrast_min, contrast_max)) + normalizer = matplotlib.colors.Normalize(vmin=v_min, vmax=v_max) + elif contrast_method == 'absolute': + normalizer = matplotlib.colors.Normalize(vmin=contrast_min, vmax=contrast_max) + else: + normalizer = None + color_map = copy.copy(cm.get_cmap(image_settings["color_map"])) + color_map.set_bad(color=image_settings["nan_color"]) + + self.axes.imshow(image, zorder=0, cmap=color_map, + extent=[self.data_range[1][0], self.data_range[1][1], self.data_range[0][1], + self.data_range[0][0]], norm=normalizer) + + def start_animation(self, image_number, visualization_settings, animation_skip_points=10): + self.animation_skip_points = animation_skip_points + self.render_image(image_number, visualization_settings["image_settings"]) + self.animation_array = self.model_view.get_1d_signal_by_number(image_number) + self.animation = FuncAnimation(self.figure, self.animation_update, + interval=1, frames=int(len(self.animation_array) / self.animation_skip_points)) + plt.show() + # ani.save("test_first_half.gif", fps=20) diff --git a/view/plotable_dialog.py b/view/plotable_dialog.py new file mode 100644 index 0000000..c945d7b --- /dev/null +++ b/view/plotable_dialog.py @@ -0,0 +1,50 @@ + + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.matplotlib_chart import chart +from view.ui import ui_dialog_plotable + + +class PlotableDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_plotable.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.ui_dialog.comboBox_plot_information.currentIndexChanged.connect(self.change_combo_box) + self.ui_dialog.checkBox_show_empty_data_sets.stateChanged.connect(self.handle_show_empty_check_box) + self.set_up_render_window() + self.set_up_combo_box() + self.change_combo_box() + + def set_up_render_window(self): + self.render_window = chart.CustomQtChart(self.ui_dialog.widget) + + def change_combo_box(self): + self.update_render_window() + + def handle_show_empty_check_box(self): + self.update_render_window() + + def update_render_window(self): + current_key = self.ui_dialog.comboBox_plot_information.currentText() + if current_key == "": + return + plotable_dict = self.model_view.get_plot_dict() + data = [] + show_empty_data_sets = self.ui_dialog.checkBox_show_empty_data_sets.isChecked() + if current_key != 'empty_data_sets' and not show_empty_data_sets: + for index in range(len(plotable_dict['empty_data_sets'])): + if not plotable_dict['empty_data_sets'][index]: + data.append(plotable_dict[current_key][index]) + else: + data = plotable_dict[current_key] + self.render_window.render(data) + + def set_up_combo_box(self): + plotable_dict = self.model_view.get_plot_dict() + for key in plotable_dict: + self.ui_dialog.comboBox_plot_information.addItem(str(key)) diff --git a/view/point_cloud_dialog.py b/view/point_cloud_dialog.py new file mode 100644 index 0000000..b9a1743 --- /dev/null +++ b/view/point_cloud_dialog.py @@ -0,0 +1,80 @@ + + +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view.image_visualization.vtk import qt_point_cloud_render_window +from view.ui import ui_dialog_point_cloud + + +class PointCloudDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.image_number = image_number + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_point_cloud.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.set_up_ui_fields() + self.setWindowState(QtCore.Qt.WindowMaximized) + self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) + self.set_up_render_window() + self.update_render_window() + self.ui_dialog.spinBox_image_number.editingFinished.connect(self.update_render_window) + self.ui_dialog.checkBox_show_points.clicked.connect(self.update_render_window) + self.ui_dialog.checkBox_grey_color_map.clicked.connect(self.update_render_window) + self.ui_dialog.doubleSpinBox_percentile_max.valueChanged.connect(self.update_render_window) + self.ui_dialog.doubleSpinBox_percentile_min.valueChanged.connect(self.update_render_window) + self.ui_dialog.doubleSpinBox_z_factor.valueChanged.connect(self.update_render_window) + + def set_up_render_window(self): + self.render_window = qt_point_cloud_render_window.PointCloudVtkQtRenderWindow( + widget=self.ui_dialog.widget_render_window, model_view=self.model_view) + self.update_render_window() + + def set_up_ui_fields(self): + max_image_index = self.model_view.get_maximum_image_index() + z_scale = self.model_view.get_config_by_key_sub_key("render_point_cloud", "z_scale") + show_points = self.model_view.get_config_by_key_sub_key("render_point_cloud", "show_points") + greyscale = self.model_view.get_config_by_key_sub_key("render_point_cloud", "greyscale") + percentile = self.model_view.get_config_by_key_sub_key("render_point_cloud", "color_map_percentile") + self.ui_dialog.doubleSpinBox_z_factor.setValue(z_scale) + self.ui_dialog.checkBox_show_points.setChecked(show_points) + self.ui_dialog.checkBox_grey_color_map.setChecked(greyscale) + self.ui_dialog.doubleSpinBox_percentile_min.setValue(percentile[0]) + self.ui_dialog.doubleSpinBox_percentile_max.setValue(percentile[1]) + self.ui_dialog.spinBox_image_number.setMaximum(max_image_index) + self.ui_dialog.spinBox_image_number.setValue(self.image_number) + + def update_render_window(self): + image_number = self.ui_dialog.spinBox_image_number.value() + show_points = self.ui_dialog.checkBox_show_points.isChecked() + greyscale = self.ui_dialog.checkBox_grey_color_map.isChecked() + z_factor = self.ui_dialog.doubleSpinBox_z_factor.value() + color_map_percentile = [self.ui_dialog.doubleSpinBox_percentile_min.value(), + self.ui_dialog.doubleSpinBox_percentile_max.value()] + self.render_window.render(image_number=image_number, show_points=show_points, z_scale=z_factor, + greyscale=greyscale, + color_map_percentiles=color_map_percentile) + self.render_window.reset_render_camera() + self.dump_ui_to_config() + + def handle_combo_box_changed(self): + self.update_render_window() + + def handle_image_number_changed(self): + self.image_number = self.ui_dialog.spinBox_image_number.value() + self.update_render_window() + + def dump_ui_to_config(self): + z_scale = self.ui_dialog.doubleSpinBox_z_factor.value() + show_points = self.ui_dialog.checkBox_show_points.isChecked() + greyscale = self.ui_dialog.checkBox_grey_color_map.isChecked() + percentile = [self.ui_dialog.doubleSpinBox_percentile_min.value(), + self.ui_dialog.doubleSpinBox_percentile_max.value()] + self.model_view.set_config_by_key_sub_key("render_point_cloud", "z_scale", z_scale) + self.model_view.set_config_by_key_sub_key("render_point_cloud", "show_points", show_points) + self.model_view.set_config_by_key_sub_key("render_point_cloud", "greyscale", greyscale) + self.model_view.set_config_by_key_sub_key("render_point_cloud", "color_map_percentile", percentile) + + def accept(self) -> None: + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/progress_representations.py b/view/progress_representations.py new file mode 100644 index 0000000..15bb8aa --- /dev/null +++ b/view/progress_representations.py @@ -0,0 +1,70 @@ +import logging +from abc import abstractmethod + +from PyQt5 import QtWidgets, QtCore + +log = logging.getLogger(__name__) + + +class ProgressRepresentation: + @abstractmethod + def set_progress_text(self, text): + pass + + @abstractmethod + def set_progress_percentage_value(self, percentage): + pass + + @abstractmethod + def cancel_progress(self): + pass + + @abstractmethod + def process_finished(self): + pass + + @abstractmethod + def process_started(self, process_name): + pass + + @abstractmethod + def set_breakable(self, breakable): + pass + + @abstractmethod + def process_events(self): + pass + + +class ProgressDialogQt(ProgressRepresentation): + def __init__(self, parent_widget, model_view): + self.parent_widget = parent_widget + self.model_view = model_view + self.progress_dialog = None + self.dialog_open = False + + def process_started(self, process_name): + if self.dialog_open is True: + return False + self.use_qt_progress_bar(process_name=process_name) + self.dialog_open = True + QtWidgets.QApplication.processEvents() + return True + + def use_qt_progress_bar(self, process_name): + self.progress_dialog = QtWidgets.QProgressDialog("Current Process: {}".format(process_name), None, 0, 0) + + self.progress_dialog.setWindowFlags(self.progress_dialog.windowFlags() | QtCore.Qt.CustomizeWindowHint) + self.progress_dialog.setWindowFlags(self.progress_dialog.windowFlags() & ~QtCore.Qt.WindowCloseButtonHint) + self.progress_dialog.setRange(0, 0) + self.progress_dialog.setWindowTitle("Progress") + self.progress_dialog.setValue(0) + self.progress_dialog.setModal(True) + self.progress_dialog.setMinimumDuration(200) + + def process_finished(self): + self.dialog_open = False + self.progress_dialog.close() + + def process_events(self): + QtWidgets.QApplication.processEvents() diff --git a/view/qt_custom_dialogs/__init__.py b/view/qt_custom_dialogs/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/qt_custom_dialogs/__init__.py @@ -0,0 +1 @@ + diff --git a/view/qt_custom_dialogs/rename_dialog.py b/view/qt_custom_dialogs/rename_dialog.py new file mode 100644 index 0000000..d68a9bf --- /dev/null +++ b/view/qt_custom_dialogs/rename_dialog.py @@ -0,0 +1,33 @@ + + +from PyQt5 import QtWidgets, QtCore + + +class RenameDialog(QtWidgets.QDialog): + def __init__(self, previous_name, model_view, parent=None): + super().__init__(parent=parent) + self.setWindowTitle("Rename Dialog") + self.previous_name = previous_name + self.model_view = model_view + self.layout = QtWidgets.QGridLayout() + self.label_old_name = QtWidgets.QLabel(text=str(previous_name)) + self.line_edit_new_name = QtWidgets.QLineEdit() + self.line_edit_new_name.setText(str(previous_name)) + self.button_box = QtWidgets.QDialogButtonBox() + self.button_box.setOrientation(QtCore.Qt.Horizontal) + self.button_box.setStandardButtons( + QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.button_box.layout().setDirection( + QtWidgets.QBoxLayout.RightToLeft) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + self.layout.addWidget(self.label_old_name) + self.layout.addWidget(self.line_edit_new_name) + self.layout.addWidget(self.button_box) + + self.setLayout(self.layout) + + def accept(self): + new_name = self.line_edit_new_name.text() + self.model_view.rename_feature_class(self.previous_name, new_name) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/qt_custom_widgets/__init__.py b/view/qt_custom_widgets/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/qt_custom_widgets/__init__.py @@ -0,0 +1 @@ + diff --git a/view/qt_custom_widgets/right_click_menu.py b/view/qt_custom_widgets/right_click_menu.py new file mode 100644 index 0000000..13b5cf9 --- /dev/null +++ b/view/qt_custom_widgets/right_click_menu.py @@ -0,0 +1,15 @@ + + +from PyQt5 import QtWidgets + + +class ListWidgetRightClickMenu(QtWidgets.QMenu): + def __init__(self, text_call_back_function_dict): + super().__init__() + self.action_list = [] + for text, call_back_function in text_call_back_function_dict.items(): + action = QtWidgets.QAction( + text=text) # triggered the activation events after the right-click menu is. Here slef.close call the system comes close event. + self.action_list.append(action) + self.addAction(action) + action.triggered.connect(call_back_function) diff --git a/view/rescale_feature_dialog.py b/view/rescale_feature_dialog.py new file mode 100644 index 0000000..37110f9 --- /dev/null +++ b/view/rescale_feature_dialog.py @@ -0,0 +1,28 @@ + + +from PyQt5 import QtWidgets + +from model_view import frontend_adapter +from view.ui import ui_dialog_rescale_features + + +class RescaleFeatureDialog(QtWidgets.QDialog): + def __init__(self, model_view, parent=None): + QtWidgets.QDialog.__init__(self, parent, ) + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_rescale_features.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.ui_dialog.lineEdit_x_factor.setText(str(1)) + self.ui_dialog.lineEdit_y_factor.setText(str(1)) + self.ui_dialog.pushButton_auto_scale.clicked.connect(self.handle_auto_scale) + + def handle_auto_scale(self): + step_size = self.model_view.get_step_size() + self.ui_dialog.lineEdit_x_factor.setText(str(1 / step_size[0])) + self.ui_dialog.lineEdit_y_factor.setText(str(1 / step_size[1])) + + def accept(self) -> None: + x_formula = self.ui_dialog.lineEdit_x_factor.text() + y_formula = self.ui_dialog.lineEdit_y_factor.text() + self.model_view.rescale_features_by_formula(x_formula, y_formula) + self.done(QtWidgets.QDialog.Accepted) diff --git a/view/saasmi_dialog.py b/view/saasmi_dialog.py new file mode 100644 index 0000000..474ee85 --- /dev/null +++ b/view/saasmi_dialog.py @@ -0,0 +1,633 @@ +import logging +import os + +import matplotlib.pyplot +from PyQt5 import QtWidgets, QtCore + +from model_view import frontend_adapter +from view import color_map_picker_dialog, export_xyz_dialog +from view.saasmi_visualization.matplotlib import saasmi_render_window as matplotlib_render_window, \ + saasmi_picked_key_visualization +from view.saasmi_visualization.vtk import saasmi_render_window as vtk_render_window +from view.ui import ui_dialog_saasmi + +log = logging.getLogger(__name__) + + +class SaasmiDialog(QtWidgets.QDialog): + def __init__(self, model_view, image_number, feature_class_index, beta=250, parent=None, auto_generate_rag=True): + QtWidgets.QDialog.__init__(self, parent) + self.visualization_backend = {"vtk": vtk_render_window.GraphVisualization, + "matplotlib": matplotlib_render_window.GraphVisualization} + self.beta = beta + self.image_number = image_number + self.feature_class_index = feature_class_index + self.source_node = None + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.ui_dialog = ui_dialog_saasmi.Ui_Dialog() + self.ui_dialog.setupUi(self) + self.set_up_settings() + self.current_element = None + self.auto_generate_rag = auto_generate_rag + self.ui_dialog.tabWidget_saasmi.setCurrentIndex(0) + self.setWindowFlags( + QtCore.Qt.Window | + QtCore.Qt.WindowMaximizeButtonHint | + QtCore.Qt.WindowMinimizeButtonHint) + max_image_number = self.model_view.get_maximum_image_index() + self.ui_dialog.spinBox_image_number.setMaximum(max_image_number) + self.ui_dialog.spinBox_image_number.setValue(image_number) + if self.feature_class_index is None: + self.feature_class_index = feature_class_index + self.update_windows() + self.ui_dialog.spinBox_image_number.valueChanged.connect(self.reset_ui) + self.ui_dialog.pushButton_remove_element.clicked.connect(self.handle_remove_element) + self.ui_dialog.radioButton_add_edges_by_nodes.toggled.connect(self.handle_click_add_edges_radio_button) + color_maps = matplotlib.pyplot.colormaps() + self.ui_dialog.comboBox_rag_color_map.addItem("FHI-Color-Map") + self.ui_dialog.comboBox_rag_color_map.addItems(color_maps) + self.ui_dialog.comboBox_node_color_map.addItem("FHI-Color-Map") + self.ui_dialog.comboBox_node_color_map.addItems(color_maps) + current_rag_color_map = self.model_view.get_config_by_key_sub_key("saasmi", "rag_color_map") + self.ui_dialog.comboBox_rag_color_map.setCurrentText(current_rag_color_map) + self.ui_dialog.pushButton_force_recreate.clicked.connect(self.handle_force_recreate_button) + self.ui_dialog.pushButton_export_as_image.clicked.connect(self.handle_export_as_image) + self.ui_dialog.pushButton_update_settings.clicked.connect(self.handle_update_settings) + self.ui_dialog.pushButton_force_recreate_atom_network.clicked.connect(self.handle_force_recreate_atom_network) + self.ui_dialog.pushButton_atom_1_color.clicked.connect(self.handle_atom_1_color_button) + self.ui_dialog.pushButton_atom_2_color.clicked.connect(self.handle_atom_2_color_button) + self.ui_dialog.pushButton_export_as_csv.clicked.connect(self.handle_export_as_csv) + self.ui_dialog.pushButton_export_rag.clicked.connect(self.handle_export_rag) + self.ui_dialog.pushButton_export_chosen_network.clicked.connect(self.handle_export_graph) + self.ui_dialog.pushButton_export_rag_properties.clicked.connect(self.handle_export_rag_properties) + self.ui_dialog.comboBox_graph_selection.currentIndexChanged.connect(self.handle_graph_selection_combo_box) + self.ui_dialog.doubleSpinBox_edge_radius.valueChanged.connect(self.handle_edge_radius_changed) + self.ui_dialog.doubleSpinBox_node_radius.valueChanged.connect(self.handle_node_radius_changed) + self.ui_dialog.pushButton_rag_edge_color.clicked.connect(self.handle_rag_color_picker) + self.ui_dialog.pushButton_ring_color.clicked.connect(self.handle_network_color_picker) + self.ui_dialog.comboBox_visualization_backend.currentTextChanged.connect( + self.handle_visualization_backend_combo_box) + self.ui_dialog.pushButton_browse_file_system.clicked.connect(self.handle_open_file_dialog) + self.ui_dialog.tabWidget_saasmi.currentChanged.connect(self.handle_saasmi_tab_changed) + self.ui_dialog.pushButton_export_as_xyz.clicked.connect(self.handle_export_as_xyz) + self.ui_dialog.pushButton_add_image_mask.clicked.connect(self.handle_add_mask_by_file_path) + self.ui_dialog.pushButton_remove_image_mask.clicked.connect(self.handle_remove_image_mask) + self.ui_dialog.pushButton_remove_polygon.clicked.connect(self.handle_remove_polygon) + self.ui_dialog.pushButton_check_edges_with_image.clicked.connect(self.handle_check_edges_button) + self.ui_dialog.checkBox_mark_incorrect_edges.clicked.connect(self.handle_mark_incorrect_edges_check_box) + self.reset_render_camera() + + def __set_up_node_settings(self): + current_node_color_map = self.model_view.get_config_by_key_sub_key("saasmi", "node_color_map") + node_radius = self.model_view.get_config_by_key_sub_key("saasmi", "node_radius") + edge_radius = self.model_view.get_config_by_key_sub_key("saasmi", "edge_radius") + self.ui_dialog.comboBox_node_color_map.setCurrentText(current_node_color_map) + self.ui_dialog.doubleSpinBox_node_radius.setValue(node_radius) + self.ui_dialog.doubleSpinBox_edge_radius.setValue(edge_radius) + + def __set_up_visualization_backend(self): + self.ui_dialog.comboBox_visualization_backend.addItems(list(self.visualization_backend.keys())) + visualization_backend = self.model_view.get_config_by_key_sub_key("saasmi", "visualization_backend") + if visualization_backend is not None: + self.ui_dialog.comboBox_visualization_backend.setCurrentText(visualization_backend) + else: + self.ui_dialog.comboBox_visualization_backend.setCurrentIndex(0) + self.set_visualization_backend() + + def set_visualization_backend(self): + chosen_backend = self.visualization_backend[self.ui_dialog.comboBox_visualization_backend.currentText()] + self.saasmi_graph = chosen_backend(widget=self.ui_dialog.widget_saasmi_graph, + model_view=self.model_view) + self.picked_key_render_window = saasmi_picked_key_visualization.PickedKeyVisualization( + widget=self.ui_dialog.widget_picked_key_render_window, model_view=self.model_view) + self.saasmi_graph.left_click_position.connect(self.handle_left_click_in_graph_window) + self.saasmi_graph.right_click_position.connect(self.handle_right_click_in_graph_window) + self.saasmi_graph.delete_button.connect(self.handle_delete_button_pressed_in_vtk) + self.saasmi_graph.lasso_selection.connect(self.handle_lasso_selection) + + def set_up_settings(self): + self.__set_up_visualization_backend() + self.__set_up_graph_settings() + self.__set_up_network_settings() + self.__set_up_export_dialog() + + def set_button_color(self, button, color): + button.setStyleSheet("background-color : rgb({r},{g},{b})".format(r=color[0], g=color[1], b=color[2])) + + def get_button_color(self, button: QtWidgets.QPushButton): + color = button.palette().button().color() + color = color.getRgb() + return color + + def get_color_from_color_dialog(self): + color_dialog = QtWidgets.QColorDialog() + color = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog).getRgb() + color = color[:3] + return color + + def handle_atom_1_color_button(self): + color = self.get_color_from_color_dialog() + self.model_view.set_config_by_key_sub_key("saasmi", "network_atom_1_color", color) + self.set_button_color(self.ui_dialog.pushButton_atom_1_color, color) + + def handle_atom_2_color_button(self): + color = self.get_color_from_color_dialog() + self.model_view.set_config_by_key_sub_key("saasmi", "network_atom_2_color", color) + self.set_button_color(self.ui_dialog.pushButton_atom_2_color, color) + + def __set_up_network_settings(self): + self.ui_dialog.comboBox_ring_type.addItems(["oxygen_silicon", "oxygen", "silicon"]) + self.ui_dialog.comboBox_ring_color_selector.addItems(["angle", "distance"]) + ring_type = self.model_view.get_config_by_key_sub_key("saasmi", "network_ring_type") + self.ui_dialog.comboBox_ring_type.setCurrentText(ring_type) + atom_1_radius = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_1_radius") + atom_1_color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_1_color") + atom_2_radius = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_2_radius") + atom_2_color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_2_color") + ring_thickness = self.model_view.get_config_by_key_sub_key("saasmi", "network_ring_thickness") + show_atoms = self.model_view.get_config_by_key_sub_key("saasmi", "network_show_atoms") + show_network_structure = self.model_view.get_config_by_key_sub_key("saasmi", "network_show_network_structure") + show_network_polygons = self.model_view.get_config_by_key_sub_key("saasmi", "network_show_network_polygons") + edge_color_selector = self.model_view.get_config_by_key_sub_key("saasmi", "network_edge_color_selector") + self.ui_dialog.doubleSpinBox_atom_1_radius.setValue(atom_1_radius) + self.set_button_color(self.ui_dialog.pushButton_atom_1_color, atom_1_color) + self.ui_dialog.doubleSpinBox_atom_2_radius.setValue(atom_2_radius) + self.set_button_color(self.ui_dialog.pushButton_atom_2_color, atom_2_color) + self.ui_dialog.doubleSpinBox_ring_thickness.setValue(ring_thickness) + self.ui_dialog.checkBox_show_network_structure.setChecked(show_network_structure) + self.ui_dialog.checkBox_show_network_polygons.setChecked(show_network_polygons) + self.ui_dialog.checkBox_show_network_atoms.setChecked(show_atoms) + self.ui_dialog.comboBox_ring_color_selector.setCurrentText(edge_color_selector) + self.ui_dialog.comboBox_ring_color_selector.currentTextChanged.connect( + self.handle_network_color_selector_changed) + + def __set_up_graph_settings(self): + show_graph = self.model_view.get_config_by_key_sub_key("saasmi", "show_graph") + self.ui_dialog.comboBox_graph_selection.addItems(["full graph", "subgraph"]) + self.ui_dialog.comboBox_edge_color_selector.addItems(["angle", "distance"]) + self.ui_dialog.checkBox_show_ring_graph.setChecked(show_graph) + edge_color_selector = self.model_view.get_config_by_key_sub_key("saasmi", "rag_edge_color_selector") + disk_size = self.model_view.get_config_by_key_sub_key("saasmi", "disk_size") + self.ui_dialog.doubleSpinBox_disk_size.setValue(disk_size) + self.ui_dialog.comboBox_edge_color_selector.setCurrentText(edge_color_selector) + self.ui_dialog.comboBox_edge_color_selector.currentTextChanged.connect(self.handle_rag_color_selector_changed) + self.__set_up_node_settings() + self.__set_up_labeled_image_settings() + + def __set_up_labeled_image_settings(self): + opacity = self.model_view.get_config_by_key_sub_key("saasmi", "opacity") + self.ui_dialog.spinBox_label_image_opacity.setValue(opacity) + + def __set_up_export_dialog(self): + resolution = self.model_view.get_config_by_key_sub_key("saasmi", "resolution_image") + file_path = self.model_view.get_config_by_key_sub_key("saasmi", "file_path_image") + self.ui_dialog.spinBox_resolution_x.setValue(resolution[0]) + self.ui_dialog.spinBox_resolution_y.setValue(resolution[1]) + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def reset_ui(self): + self.source_node = None + self.current_element = None + self.update_windows() + + def get_graph_settings(self): + graph_settings = {} + graph_settings["node_radius"] = self.ui_dialog.doubleSpinBox_node_radius.value() + graph_settings["edge_radius"] = self.ui_dialog.doubleSpinBox_edge_radius.value() + graph_settings["show_graph"] = self.ui_dialog.checkBox_show_ring_graph.isChecked() + graph_settings['mark_incorrect_edges'] = self.ui_dialog.checkBox_mark_incorrect_edges.isChecked() + for key, value in graph_settings.items(): + self.model_view.set_config_by_key_sub_key("saasmi", key, value) + return graph_settings + + def get_network_settings(self): + network_settings = {} + network_settings["atom_1_radius"] = self.ui_dialog.doubleSpinBox_atom_1_radius.value() + network_settings["atom_2_radius"] = self.ui_dialog.doubleSpinBox_atom_2_radius.value() + network_settings["show_atoms"] = self.ui_dialog.checkBox_show_network_atoms.isChecked() + network_settings["show_network_structure"] = self.ui_dialog.checkBox_show_network_structure.isChecked() + network_settings["show_network_polygons"] = self.ui_dialog.checkBox_show_network_polygons.isChecked() + network_settings["ring_thickness"] = self.ui_dialog.doubleSpinBox_ring_thickness.value() + network_settings["ring_type"] = self.ui_dialog.comboBox_ring_type.currentText() + for key, value in network_settings.items(): + self.model_view.set_config_by_key_sub_key("saasmi", "network_" + key, value) + return network_settings + + def reset_render_camera(self): + self.saasmi_graph.reset_render_camera() + + def update_windows(self): + image_number = self.ui_dialog.spinBox_image_number.value() + if self.current_element is not None: + picked_key = self.current_element[1] + else: + picked_key = None + label_image_opacity = self.ui_dialog.spinBox_label_image_opacity.value() / 100 + graph_settings = self.get_graph_settings() + network_settings = self.get_network_settings() + graph_selection = self.ui_dialog.comboBox_graph_selection.currentText() + if graph_selection == "subgraph": + apply_image_mask = True + else: + apply_image_mask = False + if self.ui_dialog.tabWidget_saasmi.currentIndex() == 1: + render_analyse_area_mask = True + else: + render_analyse_area_mask = False + disk_size = self.ui_dialog.doubleSpinBox_disk_size.value() + self.saasmi_graph.render(image_number=image_number, picked_key=picked_key, source_node=self.source_node, + beta=self.beta, disk_size=disk_size, label_image_opacity=label_image_opacity, + graph_settings=graph_settings, + network_settings=network_settings, render_analyse_area_mask=render_analyse_area_mask, + apply_image_mask=apply_image_mask, auto_generate_rag=self.auto_generate_rag) + graph_type = self.get_graph_type_for_picking() + self.picked_key_render_window.render(image_number=image_number, element_key=picked_key, graph_type=graph_type) + self.saasmi_graph.update() + + def handle_remove_element(self): + if self.current_element is None: + return + image_number = self.ui_dialog.spinBox_image_number.value() + element_type, element_key = self.current_element + graph_type = self.get_graph_type_for_picking() + if self.ui_dialog.radioButton_select_graph.isChecked() or self.ui_dialog.radioButton_select_network.isChecked(): + if element_type == "edge": + self.model_view.remove_saasmi_rag_edge(image_number=image_number, key=element_key, + graph_type=graph_type) + self.current_element = None + self.update_windows() + elif element_type == "node": + self.model_view.saasmi_remove_node(image_number=image_number, node_id=element_key, + graph_type=graph_type) + self.current_element = None + self.update_windows() + self.update_element_ui() + + def handle_click_add_edges_radio_button(self): + if not self.ui_dialog.radioButton_add_edges_by_nodes.isChecked(): + self.source_node = None + self.update_windows() + elif self.current_element is not None: + element_type, element_key = self.current_element + if element_type == "node" and self.source_node is None: + self.source_node = element_key + self.update_windows() + + def update_element_ui(self): + image_number = self.ui_dialog.spinBox_image_number.value() + try: + element_type, element_key = self.current_element + except: + return + self.ui_dialog.label_element_information.setText("{} {}".format(element_type, element_key)) + self.ui_dialog.listWidget_element_information.clear() + if element_type == "node": + node_information_dict = self.model_view.get_saasmi_node_information(image_number, element_key, + graph_type=self.get_graph_type_for_picking()) + node_information = [] + for key, value in node_information_dict.items(): + node_information.append("{}: {}".format(key, value)) + self.ui_dialog.widget_add_edge.setHidden(False) + self.ui_dialog.listWidget_element_information.setHidden(False) + self.ui_dialog.pushButton_remove_element.setText("Remove all Edges \n from Node") + self.ui_dialog.listWidget_element_information.addItems(node_information) + elif element_type == "edge": + graph_type = self.get_graph_type_for_picking() + edge_information_dict = self.model_view.get_saasmi_edge_information(image_number, element_key, + graph_type=graph_type) + edge_information = [] + for key, value in edge_information_dict.items(): + edge_information.append("{}: {}".format(key, value)) + self.ui_dialog.widget_add_edge.setHidden(True) + self.ui_dialog.listWidget_element_information.addItems(edge_information) + self.ui_dialog.pushButton_remove_element.setText("Remove Edge") + + def handle_right_click_in_graph_window(self, position): + element_key, element_type = self.model_view.saasmi_get_nearest_element(self.image_number, position, + graph_type=None) + if element_type == "node": + self.ui_dialog.radioButton_add_edges_by_nodes.setChecked(True) + self.source_node = element_key + self.update_windows() + self.update_element_ui() + + def handle_delete_button_pressed_in_vtk(self): + self.handle_remove_element() + + def _get_area_type(self): + if self.ui_dialog.radioButton_negative_area.isChecked(): + return "negative" + else: + return "positive" + + def update_area_list_widget(self): + areas = self.model_view.get_analyse_areas_string_list(self.image_number) + self.ui_dialog.listWidget_polygon_areas.clear() + self.ui_dialog.listWidget_polygon_areas.addItems(areas) + + def handle_lasso_selection(self, polygon_points): + area_type = self._get_area_type() + self.model_view.add_saasmi_area_analyser_polygon(self.image_number, polygon=polygon_points, area_type=area_type) + self.update_area_list_widget() + self.update_windows() + + def add_edges_by_click(self, element_type, element_key): + if not self.ui_dialog.radioButton_add_edges_by_nodes.isChecked(): + self.source_node = None + return + image_number = self.ui_dialog.spinBox_image_number.value() + if element_type == "node": + if self.source_node is None: + self.source_node = element_key + return + start_node = self.source_node + end_node = element_key + node_key = (start_node, end_node) + self.model_view.add_saasmi_rag_edge(image_number, node_key) + + def handle_edge_radius_changed(self): + if self.ui_dialog.doubleSpinBox_edge_radius.value() == 0: + self.ui_dialog.radioButton_select_graph.setChecked(True) + self.model_view.set_config_by_key_sub_key("saasmi", "edge_radius", + self.ui_dialog.doubleSpinBox_edge_radius.value()) + + def handle_node_radius_changed(self): + if self.ui_dialog.doubleSpinBox_node_radius.value() == 0: + self.ui_dialog.radioButton_select_graph.setChecked(True) + self.model_view.set_config_by_key_sub_key("saasmi", "node_radius", + self.ui_dialog.doubleSpinBox_node_radius.value()) + + def handle_force_recreate_button(self): + reply = QtWidgets.QMessageBox.question(self, 'Message', + "Are you sure, all manual changes to edges will be lost ?", + QtWidgets.QMessageBox.Yes | + QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + + if reply != QtWidgets.QMessageBox.Yes: + return + else: + image_number = self.ui_dialog.spinBox_image_number.value() + disk_size = self.ui_dialog.doubleSpinBox_disk_size.value() + self.model_view.set_config_by_key_sub_key("saasmi", "disk_size", disk_size) + self.model_view.get_saasmi_rag_and_label(image_number=image_number, force_recreation=True, + disk_size=disk_size, beta=self.beta) + self.reset_ui() + + def handle_force_recreate_atom_network(self): + self.model_view.recreate_saasmi_network(self.image_number, atom_network_type="silicon_oxygen") + self.update_windows() + + def handle_export_as_image(self): + resolution = [self.ui_dialog.spinBox_resolution_x.value(), self.ui_dialog.spinBox_resolution_y.value()] + file_path = self.ui_dialog.lineEdit_file_path.text() + self._validate_file_path(file_path) + self.model_view.set_config_by_key_sub_key("saasmi", "resolution_image", resolution) + self.model_view.set_config_by_key_sub_key("saasmi", "file_path_image", file_path) + self.saasmi_graph.export_as_image(resolution, file_path=file_path) + self.update_windows() + + def handle_export_as_xyz(self): + export_dialog = export_xyz_dialog.ExportXYZDialog(model_view=self.model_view, parent=self) + if export_dialog.exec_(): + graph_type = self.ui_dialog.comboBox_ring_type.currentText() + file_path = self.model_view.get_config_by_key_sub_key("export_xyz", "file_path") + comment = self.model_view.get_config_by_key_sub_key("export_xyz", "comment") + x_stretch = self.model_view.get_config_by_key_sub_key("export_xyz", "x_stretch_factor") + y_stretch = self.model_view.get_config_by_key_sub_key("export_xyz", "y_stretch_factor") + stretch_factor = [x_stretch, y_stretch] + if self.ui_dialog.comboBox_graph_selection.currentText() == "subgraph": + apply_image_mask = True + else: + apply_image_mask = False + self.model_view.export_saasmi_as_xyz_file(self.image_number, graph_type, file_path, comment_line=comment, + stretch_factor=stretch_factor, apply_image_mask=apply_image_mask) + + @staticmethod + def _validate_file_path(file_path): + valid_file_formats = {"png", "svg", "pdf", "jpg", "eps", "ps"} + file_path_splitted = file_path.split(".") + if len(file_path_splitted) == 1: + file_path_new = file_path + ".png" + return file_path_new + elif file_path_splitted[-1] in valid_file_formats: + return file_path + else: + file_path_new = "".join(file_path_splitted[:-1]) + ".png" + log.info("Only {} files are supported, the file name was changed from {} to {}".format(valid_file_formats, + file_path, + file_path_new)) + return file_path_new + + def _add_node(self, position): + self.model_view.saasmi_add_node(saasmi_position=position, feature_class_index=self.feature_class_index, + image_number=self.image_number) + + def get_graph_type_for_picking(self): + if self.ui_dialog.radioButton_select_network.isChecked(): + graph_type = self.ui_dialog.comboBox_ring_type.currentText() + else: + graph_type = None + return graph_type + + def get_graph_type(self): + return self.ui_dialog.comboBox_ring_type.currentText() + + def _pick_nearest_element(self, position): + element_key, element_type = self.model_view.saasmi_get_nearest_element(self.image_number, position, + graph_type=self.get_graph_type_for_picking()) + self.add_edges_by_click(element_type, element_key) + self.current_element = [element_type, element_key] + self.update_windows() + self.update_element_ui() + + def handle_left_click_in_graph_window(self, position): + if self.ui_dialog.tabWidget_saasmi.currentIndex() == 0: + if self.ui_dialog.radioButton_add_nodes.isChecked(): + position = position[0:2] + self._add_node(position) + elif self.ui_dialog.radioButton_select_graph.isChecked() or self.ui_dialog.radioButton_add_edges_by_nodes.isChecked() or self.ui_dialog.radioButton_select_network.isChecked(): + self._pick_nearest_element(position) + else: + return + # elif self.ui_dialog.tabWidget_saasmi.currentIndex() == 1: + # name = self.ui_dialog.comboBox_analyse_areas.currentText() + # position = position[:2] + # self.model_view.add_saasmi_area_analyser_point(self.image_number, name=name, point=position) + self.update_windows() + + @QtCore.pyqtSlot() + def handle_update_settings(self): + self.model_view.set_config_by_key_sub_key("saasmi", "rag_color_map", + self.ui_dialog.comboBox_rag_color_map.currentText()) + + self.model_view.set_config_by_key_sub_key("saasmi", "edge_radius", + self.ui_dialog.doubleSpinBox_edge_radius.value()) + + self.model_view.set_config_by_key_sub_key("saasmi", "node_radius", + self.ui_dialog.doubleSpinBox_node_radius.value()) + + self.model_view.set_config_by_key_sub_key("saasmi", "node_color_map", + self.ui_dialog.comboBox_node_color_map.currentText()) + + self.model_view.set_config_by_key_sub_key("saasmi", "rag_color_map", + self.ui_dialog.comboBox_rag_color_map.currentText()) + self.model_view.set_config_by_key_sub_key("saasmi", "opacity", + self.ui_dialog.spinBox_label_image_opacity.value()) + + self.update_windows() + + def handle_export_as_csv(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path = self.model_view.get_config_by_key_sub_key("saasmi", "export_csv_file_path") + if not file_path: + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', file_path, "csv file (*.csv)", + options=options) + if file_path: + self.model_view.export_atom_network_as_csv(self.image_number, file_path=file_path) + self.model_view.set_config_by_key_sub_key("saasmi", "export_csv_file_path", file_path) + + def handle_export_rag(self): + self.model_view.export_saasmi_rag(self.image_number) + + def handle_export_graph(self): + graph_type = self.get_graph_type() + graph_exported = self.model_view.export_chosen_network_graph(self.image_number, graph_type=graph_type) + if graph_exported is False: + QtWidgets.QMessageBox.information(self, 'Message', + "Graph could not be exported") + + def handle_export_rag_properties(self): + if self.ui_dialog.comboBox_graph_selection.currentText() == "subgraph": + apply_image_mask = True + else: + apply_image_mask = False + self.model_view.export_saasmi_properties(self.image_number, apply_image_mask=apply_image_mask) + + def handle_visualization_backend_combo_box(self): + chosen_backend_str = self.ui_dialog.comboBox_visualization_backend.currentText() + self.model_view.set_config_by_key_sub_key("saasmi", "visualization_backend", chosen_backend_str) + self.set_visualization_backend() + self.update_windows() + self.saasmi_graph.reset_render_camera() + + def handle_saasmi_tab_changed(self): + current_index = self.ui_dialog.tabWidget_saasmi.currentIndex() + if current_index == 0: + self.saasmi_graph.change_interactor_style(interactor_style_str=None) + elif current_index == 1: + self.saasmi_graph.change_interactor_style(interactor_style_str="lasso_selection") + self.update_windows() + + def handle_graph_selection_combo_box(self): + self.update_windows() + + def handle_rag_color_picker(self): + color_selector = self.ui_dialog.comboBox_edge_color_selector.currentText() + color_map_dict = self.model_view.get_saasmi_edge_color_bar("rag", color_selector) + graph_selection = self.ui_dialog.comboBox_graph_selection.currentText() + if graph_selection == "subgraph": + apply_image_mask = True + else: + apply_image_mask = False + rag_edge_distances = self.model_view.get_saasmi_distance_list(self.image_number, graph_type="rag", + apply_image_mask=apply_image_mask) + if self.ui_dialog.comboBox_edge_color_selector.currentText() == "angle": + color_range = [0, 180] + else: + color_range = [min(rag_edge_distances), max(rag_edge_distances)] + dialog = color_map_picker_dialog.ColorMapDialog(model_view=self.model_view, color_range=color_range, + color_map_dict=color_map_dict) + + if dialog.exec_(): + color_map_dict = dialog.color_map_dict + self.model_view.set_saasmi_edge_color_bar("rag", color_selector, color_map_dict) + self.model_view.set_config_by_key_sub_key("saasmi", + "rag_" + color_selector + "_color_map_dict", color_map_dict) + + def handle_network_color_picker(self): + color_selector = self.ui_dialog.comboBox_ring_color_selector.currentText() + color_map_dict = self.model_view.get_saasmi_edge_color_bar("network", color_selector) + graph_selection = self.ui_dialog.comboBox_graph_selection.currentText() + if graph_selection == "subgraph": + apply_image_mask = True + else: + apply_image_mask = False + ring_type = self.get_graph_type() + network_edge_distances = self.model_view.get_saasmi_distance_list(self.image_number, graph_type=ring_type, + apply_image_mask=apply_image_mask) + if self.ui_dialog.comboBox_ring_color_selector.currentText() == "angle": + color_range = [0, 180] + else: + color_range = [min(network_edge_distances), max(network_edge_distances)] + dialog = color_map_picker_dialog.ColorMapDialog(model_view=self.model_view, color_range=color_range, + color_map_dict=color_map_dict) + + if dialog.exec_(): + color_map_dict = dialog.color_map_dict + self.model_view.set_config_by_key_sub_key("saasmi", + "network_" + color_selector + "_color_map_dict", color_map_dict) + self.model_view.set_saasmi_edge_color_bar("network", color_selector, color_map_dict) + + def handle_network_color_selector_changed(self): + color_selector = self.ui_dialog.comboBox_ring_color_selector.currentText() + self.model_view.set_config_by_key_sub_key("saasmi", "network_edge_color_selector", color_selector) + + def handle_rag_color_selector_changed(self): + color_selector = self.ui_dialog.comboBox_edge_color_selector.currentText() + self.model_view.set_config_by_key_sub_key("saasmi", "rag_edge_color_selector", color_selector) + + def handle_open_file_dialog(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path = self.ui_dialog.lineEdit_file_path.text() + if file_path == "": + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save As', file_path, "All Files(*.*)", + options=options) + self.ui_dialog.lineEdit_file_path.setText(file_path) + + def handle_add_mask_by_file_path(self): + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + file_path = self.model_view.get_config_by_key_sub_key("saasmi", "image_mask_file_path") + if file_path == "": + file_path = os.getcwd() + file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "open", directory=file_path, options=options) + if file_path is not "": + self.model_view.set_config_by_key_sub_key("saasmi", "image_mask_file_path", file_path) + self.model_view.add_saasmi_area_analyser_input_mask(image_number=self.image_number, + image_mask_file_path=file_path) + self.update_image_mask_list_widget() + self.update_windows() + + def update_image_mask_list_widget(self): + self.ui_dialog.listWidget_image_masks.clear() + image_masks_file_paths = self.model_view.get_analyse_input_image_mask_file_path_list(self.image_number) + self.ui_dialog.listWidget_image_masks.addItems(image_masks_file_paths) + + def handle_remove_image_mask(self): + mask_index = self.ui_dialog.listWidget_image_masks.currentRow() + if mask_index >= 0: + self.model_view.remove_analyse_input_mask_file_path_list(image_number=self.image_number, + mask_index=mask_index) + self.update_image_mask_list_widget() + self.update_windows() + + def handle_remove_polygon(self): + polygon_index = self.ui_dialog.listWidget_polygon_areas.currentRow() + if polygon_index >= 0: + self.model_view.remove_analyse_polygon(image_number=self.image_number, + polygon_index=polygon_index) + self.update_area_list_widget() + self.update_windows() + + def handle_check_edges_button(self): + image_number = self.image_number + graph_type = "rag" + self.model_view.check_saasmi_edges(image_number=image_number, graph_type=graph_type, number_of_points=100) + self.update_windows() + + def handle_mark_incorrect_edges_check_box(self): + self.update_windows() diff --git a/view/saasmi_visualization/__init__.py b/view/saasmi_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/saasmi_visualization/matplotlib/__init__.py b/view/saasmi_visualization/matplotlib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/saasmi_visualization/matplotlib/lasso_interactor.py b/view/saasmi_visualization/matplotlib/lasso_interactor.py new file mode 100644 index 0000000..e69de29 diff --git a/view/saasmi_visualization/matplotlib/saasmi_picked_key_visualization.py b/view/saasmi_visualization/matplotlib/saasmi_picked_key_visualization.py new file mode 100644 index 0000000..f837666 --- /dev/null +++ b/view/saasmi_visualization/matplotlib/saasmi_picked_key_visualization.py @@ -0,0 +1,65 @@ + + +import numpy as np +from PyQt5 import QtCore, QtWidgets +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + + +class PickedKeyVisualization(QtCore.QObject): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + lasso_selection = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.axes = None + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) + self.canvas.setFocus() + self.widget = widget + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.set_up_matplotlib_interactor() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("key_press_event", self.key_pressed) + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def key_pressed(self, event): + if event.key == "delete": + self.delete_button.emit() + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.ydata, event.xdata]) + elif event.button == 3: + self.right_click_position.emit([event.ydata, event.xdata]) + + def render(self, image_number, element_key, graph_type, number_of_points=100): + self.figure.clear() + element_information = self.model_view.get_saasmi_edge_information(image_number, element_key, graph_type) + if element_information is None: + return + self.axes = self.figure.add_subplot(111) + + gradient_points = self.model_view.saasmi_get_edge_gradient(image_number, element_key, graph_type, + number_of_points) + distance = element_information["distance"] + x_axis = np.arange(len(gradient_points)) * distance / len(gradient_points) + self.axes.plot(x_axis, gradient_points) + self.axes.set_title("Line Gradient") + self.canvas.draw() diff --git a/view/saasmi_visualization/matplotlib/saasmi_prepared_images.py b/view/saasmi_visualization/matplotlib/saasmi_prepared_images.py new file mode 100644 index 0000000..430e4da --- /dev/null +++ b/view/saasmi_visualization/matplotlib/saasmi_prepared_images.py @@ -0,0 +1,15 @@ + + +import matplotlib.pyplot as plt + + +def show_prepared_images(im_denoised, im_adapthist, im_eq): + fig, (ax1, ax2, ax3) = plt.subplots(1, 3) + ax1.set_title("Denoised Image") + ax1.imshow(im_denoised) + ax2.set_title("Adapthist Image") + ax2.imshow(im_adapthist) + ax3.set_title("Equalized Image") + ax3.imshow(im_eq) + figure_manager = plt.get_current_fig_manager() + figure_manager.window.showMaximized() diff --git a/view/saasmi_visualization/matplotlib/saasmi_render_window.py b/view/saasmi_visualization/matplotlib/saasmi_render_window.py new file mode 100644 index 0000000..56cc09a --- /dev/null +++ b/view/saasmi_visualization/matplotlib/saasmi_render_window.py @@ -0,0 +1,496 @@ +import logging +import platform + +import matplotlib +import matplotlib.colors +import numpy as np +from PyQt5 import QtWidgets, QtCore +from matplotlib import cm +from matplotlib.backends import backend_qt5agg +from matplotlib.collections import LineCollection, EllipseCollection, PatchCollection +from matplotlib.patches import Circle, Polygon +from matplotlib.pyplot import Figure +from skimage import segmentation + +from model_view import frontend_adapter +from view.saasmi_visualization import saasmi_utilities + +log = logging.getLogger(__name__) +FHI_COLORS = [ + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (0.33596838, 0.18972332, 0.5256917, 0.8), + (0.0, 0.42687747, 0.71541502, 0.8), + (0.0, 0.63636364, 0.26086957, 0.8), + (1.0, 0.78656126, 0.0, 0.8), + (0.8972332, 0.11857708, 0.14229249, 0.8), + (0.69960474, 0.35968379, 0.2687747, 0.8), + (1.0, 0.0, 1.0, 0.8), +] +NODE_MAX_DEGREE = 10 + +layer_order_dict = {"image": 0, + "labeled_image": 5, + "network_polygons": 7, + "edges": 10, + "network": 15, + "atoms": 20, + "nodes": 25, + "analyse_area_image_mask": 30, } + + +class GraphVisualization(QtCore.QObject): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + lasso_selection = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.figure = Figure() + self.axes = None + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) + self.canvas.setFocus() + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + widget.setLayout(self.vl) + self.set_up_matplotlib_interactor() + + def set_up_matplotlib_interactor(self): + self.canvas.mpl_connect("key_press_event", self.key_pressed) + self.canvas.mpl_connect("button_press_event", self.mouse_button_pressed) + + def key_pressed(self, event): + if event.key == "delete": + self.delete_button.emit() + + def mouse_button_pressed(self, event): + if event.ydata is None or event.xdata is None: + return + if event.button == 1: + self.left_click_position.emit([event.ydata, event.xdata]) + elif event.button == 3: + self.right_click_position.emit([event.ydata, event.xdata]) + + def render(self, image_number, beta, picked_key=None, source_node=None, scan_index=None, label_image_opacity=1, + graph_settings=None, disk_size=10, network_settings=None, render_analyse_area_mask=False, + apply_image_mask=True, + auto_generate_rag=True): + step_size = self.model_view.get_step_size() + self.size_multiplier = 2 * step_size[0] + self.line_size_multiplator = 10 + if self.axes is not None: + axes_x_limit = self.axes.get_xlim() + axes_y_limit = self.axes.get_ylim() + else: + axes_x_limit = None + axes_y_limit = None + self.figure.clear() + self.axes = self.figure.add_subplot(111) + self.node_actor_dict = {} + self.edge_actor_dict = {} + rag, labels = self.model_view.get_saasmi_rag_and_label(image_number, beta, scan_index=scan_index, + auto_generate=auto_generate_rag, disk_size=disk_size) + self.picked_node = None + self.update_real_image_renderer(scan_index=scan_index, image_number=image_number) + if graph_settings["show_graph"] and len(rag) > 0: + self.render_graph(graph_settings=graph_settings, image_number=image_number, picked_key=picked_key, + source_node=source_node, apply_image_mask=apply_image_mask) + self.draw_rag_color_map_bar() + if label_image_opacity != 0: + self.render_labeled_image(rag, labels, label_image_opacity) + if network_settings["show_atoms"]: + self.render_network_atoms(image_number=image_number, network_settings=network_settings, + apply_image_mask=apply_image_mask) + if network_settings["show_network_structure"]: + self.render_network_structure(image_number=image_number, network_settings=network_settings, + apply_image_mask=apply_image_mask) + self.draw_network_color_map_bar() + if network_settings["show_network_polygons"]: + self.render_atom_network_polygons(image_number=image_number, apply_image_mask=apply_image_mask) + if render_analyse_area_mask is True: + self.render_analyse_area_image_mask(image_number) + if axes_y_limit and axes_x_limit: + self.axes.set_xlim(axes_x_limit) + self.axes.set_ylim(axes_y_limit) + self.canvas.draw() + + def render_analyse_area_image_mask(self, image_number): + image = self.model_view.get_analyse_area_mask_image(image_number) + self.axes.imshow(image, zorder=layer_order_dict["analyse_area_image_mask"], + alpha=0.3) + + def render_atom_network_polygons(self, image_number, apply_image_mask): + polygons = self.model_view.get_saasmi_atom_network_polygons(image_number, + apply_image_mask=apply_image_mask) + polygon_patches = [] + for polygon in polygons: + matplot_polygon_point_list = [list(reversed(points)) for points in polygon] + color = FHI_COLORS[len(polygon)] + matplot_polygon = Polygon(matplot_polygon_point_list, True, color=color) + matplot_polygon.zorder = layer_order_dict["network_polygons"] + polygon_patches.append(matplot_polygon) + patch_collection = PatchCollection(patches=polygon_patches, match_original=True) + self.axes.add_collection(patch_collection) + + @staticmethod + def generate_fhi_color_list(max_degree=NODE_MAX_DEGREE): + try: + return FHI_COLORS[0:max_degree + 1] + except IndexError: + log.info("The number of degrees exceed the number of colors in the fhi color list") + return FHI_COLORS + + @staticmethod + def generate_color_list_from_cmap(cmap_name, max_degree=NODE_MAX_DEGREE): + if cmap_name == "FHI-Color-Map": + return GraphVisualization.generate_fhi_color_list() + cmap = cm.get_cmap(cmap_name) + if hasattr(cmap, "colors"): + colors_list = list(cmap.colors) + else: + colors_list = cmap(np.arange(0, cmap.N)) + number_of_colors = len(colors_list) + color_step_size = int(number_of_colors / max_degree) + if color_step_size < 1: + color_step_size = 1 + new_color_list = [] + for i, loop_color in enumerate(colors_list): + if i % color_step_size == 0: + new_color_list.append(loop_color) + return new_color_list + + def add_graph_nodes_to_plot(self, image_number, centroid_dict, picked_key, source_node, node_radius): + degree_node_dict = self.get_degree_center_list(image_number, centroid_dict, picked_key, source_node) + + node_color_map = self.model_view.get_config_by_key_sub_key("saasmi", "node_color_map") + if isinstance(picked_key, np.integer): + picked_node_circle = Circle(list(reversed(centroid_dict[picked_key])), radius=node_radius / 2, + color=[1, 0.75, 0.79, 1]) + picked_node_circle.zorder = layer_order_dict["nodes"] + self.axes.add_patch(picked_node_circle) + if isinstance(source_node, np.integer): + source_node_circle = Circle(list(reversed(centroid_dict[source_node])), radius=node_radius / 2, + color=[1, 0.65, 0, 1]) + source_node_circle.zorder = layer_order_dict["nodes"] + self.axes.add_patch(source_node_circle) + color_list = self.generate_color_list_from_cmap(node_color_map, max_degree=NODE_MAX_DEGREE) + for degree, node_list in degree_node_dict.items(): + try: + color = color_list[degree] + except IndexError: + color = (1, 1, 1) + node_list = [list(reversed(position)) for position in node_list] + + node_radius_list = [node_radius] * len(node_list) + circle_collection = EllipseCollection(node_radius_list, node_radius_list, + np.zeros_like(node_radius_list), + offsets=node_list, units='x', + transOffset=self.axes.transData) + circle_collection.zorder = layer_order_dict["nodes"] + circle_collection.set_color(color) + self.axes.add_collection(circle_collection) + + def add_graph_edges_to_plot(self, image_number, centroid_dict, edge_radius, picked_key, apply_image_mask, + mark_incorrect_edges): + edge_list = [] + edges = self.model_view.get_saasmi_graph_edges(image_number, graph_type="rag", + apply_image_mask=apply_image_mask) + edge_color_value_list = [] + color_map, normalizer = self.get_color_map_and_normalizer(graph_type="rag") + edge_color_map_selector = self.model_view.get_config_by_key_sub_key("saasmi", + "rag_edge_color_selector") + for edge in edges: + start_point = list(reversed(centroid_dict[edge[0]])) + end_point = list(reversed(centroid_dict[edge[1]])) + edge_list.append([start_point, end_point]) + edge_information = self.model_view.get_saasmi_edge_information(image_number=image_number, key=edge, + graph_type="rag") + color_value = edge_information[edge_color_map_selector] + if mark_incorrect_edges and "correct_gradient" in edge_information and not edge_information[ + "correct_gradient"]: + edge_color_value_list.append(float('nan')) + else: + edge_color_value_list.append(color_value) + color_map.set_bad(color='red') + line_collection = LineCollection(edge_list, transOffset=self.axes.transData, cmap=color_map, norm=normalizer) + line_collection.set_linewidth(edge_radius) + line_collection.set_label("graph_edges") + line_collection.zorder = layer_order_dict["edges"] + line_collection.set_array(np.asarray(edge_color_value_list)) + self.axes.add_collection(line_collection) + + def render_graph(self, image_number, graph_settings, picked_key, source_node, apply_image_mask): + node_radius = graph_settings["node_radius"] * self.size_multiplier + edge_radius = graph_settings["edge_radius"] * self.line_size_multiplator + centroid_dict_image_mask = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type="rag", + apply_image_mask=apply_image_mask) # type: dict + centroid_dict_full = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type="rag", + apply_image_mask=False) + self.add_graph_nodes_to_plot(image_number, centroid_dict_image_mask, picked_key, source_node, node_radius) + self.add_graph_edges_to_plot(image_number, centroid_dict_full, edge_radius, picked_key, apply_image_mask, + mark_incorrect_edges=graph_settings['mark_incorrect_edges']) + + def get_degree_center_list(self, image_number, centroid_dict, picked_key, source_node): + node_degree_point_dict = {} + degree_dict = self.model_view.get_saasmi_node_degree_dict(image_number, graph_type="rag") + for key, centroid in centroid_dict.items(): + if isinstance(picked_key, np.integer) and (key == picked_key or key == source_node): + continue + node_degree = degree_dict[key] + if node_degree in node_degree_point_dict: + node_degree_point_dict[node_degree].append(centroid) + else: + node_degree_point_dict[node_degree] = [centroid] + return node_degree_point_dict + + def render_network_atoms(self, image_number, network_settings, apply_image_mask): + silicon_radius = network_settings["atom_2_radius"] * self.size_multiplier + self.add_silicon_atoms(image_number=image_number, node_radius=silicon_radius, apply_image_mask=apply_image_mask) + oxygen_radius = network_settings["atom_1_radius"] * self.size_multiplier + self.add_oxygen_atoms(image_number=image_number, node_radius=oxygen_radius, apply_image_mask=apply_image_mask) + + def add_silicon_atoms(self, image_number, node_radius, apply_image_mask): + silicon_atom_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type="silicon", + apply_image_mask=apply_image_mask) + if not silicon_atom_dict: + return + silicon_atom_list = [list(reversed(position)) for position in silicon_atom_dict.values()] + node_radius_list = [node_radius] * len(silicon_atom_list) + circle_collection = EllipseCollection(node_radius_list, node_radius_list, + np.zeros_like(node_radius_list), + offsets=silicon_atom_list, units='x', + transOffset=self.axes.transData) + color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_2_color") + color = [c / 255 for c in color] + + circle_collection.set_color(color) + circle_collection.zorder = layer_order_dict["atoms"] + circle_collection.set_label("silicon_atoms") + self.axes.add_collection(circle_collection) + + def add_oxygen_atoms(self, image_number, node_radius, apply_image_mask): + oxygen_atom_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type="oxygen", + apply_image_mask=apply_image_mask) + if not oxygen_atom_dict: + return + oxygen_atom_list = [list(reversed(position)) for position in oxygen_atom_dict.values()] + node_radius_list = [node_radius] * len(oxygen_atom_list) + circle_collection = EllipseCollection(node_radius_list, node_radius_list, + np.zeros_like(node_radius_list), + offsets=oxygen_atom_list, units='x', + transOffset=self.axes.transData) + color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_1_color") + color = [c / 255 for c in color] + + circle_collection.set_color(color) + circle_collection.set_label("oxygen_atoms") + circle_collection.zorder = layer_order_dict["atoms"] + self.axes.add_collection(circle_collection) + + def render_network_structure(self, image_number, network_settings, apply_image_mask): + line_width = network_settings["ring_thickness"] * self.line_size_multiplator + ring_type = network_settings["ring_type"] + edges = self.model_view.get_saasmi_graph_edges(image_number, graph_type=ring_type, + apply_image_mask=apply_image_mask) + node_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type=ring_type) + if len(edges) == 0: + return + color_map, normalizer = self.get_color_map_and_normalizer(graph_type=ring_type) + edge_color_map_selector = self.model_view.get_config_by_key_sub_key("saasmi", + "network_edge_color_selector") + edge_color_value_list = [] + lines = [] + for edge in edges: + new_points = [] + for point in edge: + new_points.append(list(reversed(node_dict[point]))) + lines.append(new_points) + edge_information = self.model_view.get_saasmi_edge_information(image_number=image_number, key=edge, + graph_type=ring_type) + color_value = edge_information[edge_color_map_selector] + edge_color_value_list.append(color_value) + + line_collection = LineCollection(lines, transOffset=self.axes.transData, cmap=color_map, norm=normalizer) + line_collection.set_label("atom_network") + line_collection.set_linewidth(line_width) + line_collection.set_array(np.asarray(edge_color_value_list)) + line_collection.zorder = layer_order_dict["network"] + self.axes.add_collection(line_collection) + + def update_real_image_renderer(self, scan_index, image_number): + image = self.model_view.get_2d_image_by_number(number=image_number, scan_index=scan_index) + contrast_settings = self.model_view.get_contrast_settings(scan_index) + nan_color = self.model_view.get_nan_color(scan_index) + step_size = self.model_view.get_step_size(scan_index) + min_z, max_z = np.nanmin(image), np.nanmax(image) + dimensions = image.shape + if contrast_settings[0] == "percentile": + if np.isnan(image).all(): + contrast_range = None + else: + contrast_range = np.percentile(image[~np.isnan(image)], (contrast_settings[1], contrast_settings[2])) + elif contrast_settings[0] == "absolute": + contrast_range = (contrast_settings[1], contrast_settings[2]) + else: + contrast_range = None + if contrast_range is not None: + normalizer = matplotlib.colors.Normalize(vmin=contrast_range[0], vmax=contrast_range[1]) + else: + normalizer = None + + window_extends = [0, len(image[0]) * step_size[0], len(image) * step_size[1], 0] + print(window_extends) + self.axes.imshow(image, cmap="gray", zorder=layer_order_dict["image"], norm=normalizer, + extent=window_extends) + + def render_labeled_image(self, rag, labels, label_image_opacity): + if labels is None: + return + degree_labels = self.colorize_degrees(rag, labels) + image_label_overlay = self.generate_color_image_by_labels(self.model_view, degree_labels) + border_color = self.model_view.get_config_by_key_sub_key("saasmi", "border_color") + border_color = [c / 255 for c in border_color] + step_size = self.model_view.get_step_size() + image_label_overlay = segmentation.mark_boundaries(image_label_overlay, labels, color=border_color) + image_label_overlay = self.highlight_picked_area(image_label_overlay, labels) + self.axes.imshow(image_label_overlay, zorder=layer_order_dict["labeled_image"], + extent=[0, len(image_label_overlay[0]) * step_size[0], + len(image_label_overlay) * step_size[1], + 0], + alpha=label_image_opacity) + + def reset_render_camera(self): + pass + + def update(self): + pass + + def export_as_image(self, resolution, file_path): + self.figure.savefig(file_path) + + @staticmethod + def colorize_degrees(rag, label_image): + """Colorize labels according to the degree of the corresponding node.""" + labels_degree_dict = GraphVisualization.node_degree_dict(rag, label_image) + for label in GraphVisualization.outer_labels(label_image): + labels_degree_dict[label] = -1 + for label in np.unique(label_image): + if label not in labels_degree_dict: + label_image[label_image == label] = 0 + degree_labels = np.vectorize(labels_degree_dict.__getitem__)(label_image) + return degree_labels + + @staticmethod + def generate_color_image_by_labels(model_view, degree_labels): + rag_color_map = model_view.get_config_by_key_sub_key("saasmi", "rag_color_map") + if rag_color_map == "FHI-Color-Map": + new_color_list = GraphVisualization.generate_fhi_color_list() + else: + new_color_list = GraphVisualization.generate_color_list_from_cmap(rag_color_map) + image_label_overlay = GraphVisualization.label2rgba(degree_labels, new_color_list, bg_label=-1) + # image_label_overlay = color.label2rgb(degree_labels, colors=new_color_list, bg_label=0, + # bg_color=(100, 100, 100)) + return image_label_overlay + + def highlight_picked_area(self, image: np.array, labels: np.array): + if self.picked_node is not None: + mask = labels == self.picked_node + image[mask, 3] = 255 + return image + else: + return image + + @staticmethod + def node_degree_dict(rag, label_image): + """Return dictionary with nodes as keys and degrees as values. + + Return a dictionary with the nodes of the rag that are in the interior + as keys and the degree of that nodes as values. + """ + node_degree_map = { + key: value + for (key, value) in rag.degree + if key not in GraphVisualization.outer_labels(label_image) + } + return node_degree_map + + @staticmethod + def outer_labels(labels): + """Return a set with labels of the interior.""" + labels = set( + np.unique( + np.concatenate( + (labels[0, :], labels[-1, :], labels[:, 0], labels[:, -1]) + ) + ) + ) + return labels + + @staticmethod + def label2rgba(labels, colors, bg_label=-1, bg_color=(0, 0, 0, 128)): + image_label_overlay = np.zeros((*labels.shape, 4)) + label_list = np.unique(labels) + for i in label_list: + if i == bg_label: + color = bg_color + else: + try: + color = colors[i] + except IndexError: + color = [140, 140, 140, 128] + mask = labels == i + for c in range(len(color)): + image_label_overlay[mask, c] = color[c] + return image_label_overlay + + def change_interactor_style(self, *args, **kwargs): + # TODO: IMPLEMENT THIS :) + pass + + def get_color_map_and_normalizer(self, graph_type): + if graph_type == "rag": + graph = "rag" + else: + graph = "network" + edge_color_map_selector = self.model_view.get_config_by_key_sub_key("saasmi", + "{}_edge_color_selector".format(graph)) + cmap_dict = self.model_view.get_saasmi_edge_color_bar(graph, edge_color_map_selector) + color_map, normalizer = saasmi_utilities.convert_cmap_dict_to_color_map_and_normalizer(cmap_dict) + return color_map, normalizer + + def draw_network_color_map_bar(self): + graph = "network" + edge_color_map_selector = self.model_view.get_config_by_key_sub_key("saasmi", + "{}_edge_color_selector".format(graph)) + cmap_dict = self.model_view.get_saasmi_edge_color_bar(graph, edge_color_map_selector) + color_map, color_normalizer = saasmi_utilities.convert_cmap_dict_to_color_map_and_normalizer(cmap_dict) + scalar_mappable = matplotlib.cm.ScalarMappable(norm=color_normalizer, cmap=color_map) + # create an axes on the right side of ax. The width of cax will be 5% + # of ax and the padding between cax and ax will be fixed at 0.05 inch. + self.figure.colorbar(scalar_mappable, ax=self.axes, shrink=0.8).set_label( + "Network {}".format(edge_color_map_selector)) + + def draw_rag_color_map_bar(self): + graph = "rag" + edge_color_map_selector = self.model_view.get_config_by_key_sub_key("saasmi", + "{}_edge_color_selector".format(graph)) + cmap_dict = self.model_view.get_saasmi_edge_color_bar(graph, edge_color_map_selector) + color_map, color_normalizer = saasmi_utilities.convert_cmap_dict_to_color_map_and_normalizer(cmap_dict) + scalar_mappable = matplotlib.cm.ScalarMappable(norm=color_normalizer, cmap=color_map) + # create an axes on the right side of ax. The width of cax will be 5% + # of ax and the padding between cax and ax will be fixed at 0.05 inch. + self.figure.colorbar(scalar_mappable, ax=self.axes, shrink=0.8).set_label( + "Rag {}".format(edge_color_map_selector)) diff --git a/view/saasmi_visualization/matplotlib/segmentation.py b/view/saasmi_visualization/matplotlib/segmentation.py new file mode 100644 index 0000000..d4e9e21 --- /dev/null +++ b/view/saasmi_visualization/matplotlib/segmentation.py @@ -0,0 +1,27 @@ +from skimage import color + +from view.saasmi_visualization.matplotlib.visualize import PlotVisualization + + +class SegmentationPlotVisualization(PlotVisualization): + def __init__(self, widget, model_view): + PlotVisualization.__init__(self, widget=widget, model_view=model_view) + + def render(self, image_number, beta, scan_index=None): + """Plot RAG over degree labeld regions.""" + + self.figure.clear() + ax = self.figure.add_subplot(1, 1, 1) + rag, label_image = self.model_view.get_saasmi_rag_and_label(image_number, beta, scan_index=scan_index) + image = self.model_view.get_2d_image_by_number(number=image_number, scan_index=scan_index) + fig_title = "Segmentation" + ax.imshow(color.label2rgb(label_image, image)) + ax.set_axis_off() + self.figure.canvas.set_window_title(fig_title) + self.figure.tight_layout() + self.canvas.draw() + # fig.savefig( + # str(RANGE_DIR.joinpath("".join((fig_title, ".pdf")))), + # bbox_inches="tight", + # pad_inches=0, + # ) diff --git a/view/saasmi_visualization/matplotlib/visualize.py b/view/saasmi_visualization/matplotlib/visualize.py new file mode 100644 index 0000000..a1c18c1 --- /dev/null +++ b/view/saasmi_visualization/matplotlib/visualize.py @@ -0,0 +1,62 @@ +from abc import abstractmethod + +import numpy as np +from PyQt5 import QtWidgets +from matplotlib.backends import backend_qt5agg +from matplotlib.figure import Figure + + +class PlotVisualization: + def __init__(self, widget, model_view): + if self.__dict__ == {}: + self.model_view = model_view + self.figure = Figure() + self.canvas = backend_qt5agg.FigureCanvasQTAgg(self.figure) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.vl.addWidget(self.canvas) + self.toolbar = backend_qt5agg.NavigationToolbar2QT(self.canvas, self.canvas) + self.figure.tight_layout() + + def colorize_degrees(self, rag, label_image): + """Colorize labels according to the degree of the corresponding node.""" + labels_degree_dict = self.node_degree_dict(rag, label_image) + for label in self.outer_labels(label_image): + labels_degree_dict[label] = 0 + degree_labels = np.vectorize(labels_degree_dict.__getitem__)(label_image) + return degree_labels + + def node_degree_dict(self, rag, label_image): + """Return dictionary with nodes as keys and degrees as values. + + Return a dictionary with the nodes of the rag that are in the interior + as keys and the degree of that nodes as values. + """ + node_degree_map = { + key: value + for (key, value) in rag.degree + if key not in self.outer_labels(label_image) + } + return node_degree_map + + @staticmethod + def outer_labels(labels): + """Return a set with labels of the interior.""" + labels = set( + np.unique( + np.concatenate( + (labels[0, :], labels[-1, :], labels[:, 0], labels[:, -1]) + ) + ) + ) + return labels + + @abstractmethod + def render(self, feature_class_index, feature_index): + """Abstract render method, should be overwritten by the concrete implementation""" + pass diff --git a/view/saasmi_visualization/saasmi_utilities.py b/view/saasmi_visualization/saasmi_utilities.py new file mode 100644 index 0000000..e519c27 --- /dev/null +++ b/view/saasmi_visualization/saasmi_utilities.py @@ -0,0 +1,44 @@ +import copy +from collections import OrderedDict + +import matplotlib +color_map_ordered_dict = OrderedDict() + +color_map_ordered_dict['Perceptually Uniform Sequential'] = [ + 'viridis', 'plasma', 'inferno', 'magma', 'cividis'] + +color_map_ordered_dict['Sequential'] = [ + 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', + 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', + 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'] + +color_map_ordered_dict['Sequential (2)'] = [ + 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', + 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', + 'hot', 'afmhot', 'gist_heat', 'copper'] + +color_map_ordered_dict['Diverging'] = [ + 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', + 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic'] + +color_map_ordered_dict['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv'] + +color_map_ordered_dict['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent', + 'Dark2', 'Set1', 'Set2', 'Set3', + 'tab10', 'tab20', 'tab20b', 'tab20c'] + +color_map_ordered_dict['Miscellaneous'] = [ + 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', + 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', + 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'] + +def convert_cmap_dict_to_color_map_and_normalizer(cmap_dict): + color_map_name = cmap_dict["color_map_name"] + color_min = cmap_dict["value_min"] + color_max = cmap_dict["value_max"] + color_map = copy.copy(matplotlib.pyplot.get_cmap(color_map_name)) + if cmap_dict["use_bound_colors"]: + color_map.set_under(cmap_dict["color_lower_bound"]) + color_map.set_over(cmap_dict["color_upper_bound"]) + color_normalizer = matplotlib.colors.Normalize(vmin=color_min, vmax=color_max) + return color_map, color_normalizer diff --git a/view/saasmi_visualization/vtk/__init__.py b/view/saasmi_visualization/vtk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/saasmi_visualization/vtk/lasso_interactor.py b/view/saasmi_visualization/vtk/lasso_interactor.py new file mode 100644 index 0000000..ca46617 --- /dev/null +++ b/view/saasmi_visualization/vtk/lasso_interactor.py @@ -0,0 +1,254 @@ +import numpy as np +import vtk +from vtk.util import numpy_support as nps + + +class InteractorStyleDrawPolygon(vtk.vtkInteractorStyle): + """ + STOLEN FROM https://gist.github.com/mhogg/4863cf0b91e2f85a56d52b8a6981e325 and https://discourse.vtk.org/t/python-version-of-vtkinteractorstyledrawpolygon-how-to-speed-up/4178 + InteractorStyleDrawPolygon is a re-write of the vtk class of the same name in python. + The python class vtk.vtkInteractorStyleDrawPolygon is not a full wrap of the underlying + ++ class. For example, vtkInteractorStyleDrawPolygon doesn't have a method for + 'GetPolygonPoints' in Python. It's in the header, source and documentation, but not python. + Reports are this is because it returns a vtkVector2i, a template based generic class, which + is difficult to wrap. + References: + - http://vtk.1045678.n5.nabble.com/Missing-method-for-vtkInteractorStyleDrawPolygon-td5747100.html + - https://gitlab.kitware.com/vtk/vtk/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.h + - https://gitlab.kitware.com/vtk/vtk/-/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.cxx + """ + + class vtkInternal(object): + """ + Class internal to InteractorStyleDrawPolygon to help with drawing the polygon + """ + + def __init__(self, parent=None): + super().__init__() + self._points = [] + + @property + def points(self): + return self._points + + def AddPoint(self, x, y): + self._points.append([x, y]) + + def GetPoint(self, index): + if index < 0 or index > len(self._points) - 1: return + return np.array(self._points[index]) + + def GetNumberOfPoints(self): + return len(self._points) + + def Clear(self): + self._points = [] + + def DrawPixels(self, StartPos, EndPos, pixels, size): + # C++ args: const vtkVector2i& StartPos, const vtkVector2i& EndPos, unsigned char* pixels, const int* size + # NOTE: ^ operator = bitwise exclusive OR. Same in C++ and Python + length = int(round(np.linalg.norm(StartPos - EndPos))) + if length == 0: return + x1, y1 = StartPos + x2, y2 = EndPos + x = [int(round(v)) for v in np.linspace(x1, x2, length)] + y = [int(round(v)) for v in np.linspace(y1, y2, length)] + indices = np.array([row * size[0] + col for col, row in zip(x, y)]) + pixels[indices] = 255 ^ pixels[indices] + + def __init__(self, parent=None, interactor=None, renderer=None, line_radius=1): + self.parent = parent + self.line_radius = line_radius + self.actor = None + self.interactor = interactor + self.renderer = renderer + self.AddObserver("MouseMoveEvent", self.OnMouseMove) + self.AddObserver("LeftButtonPressEvent", self.OnLeftButtonDown) + self.AddObserver("LeftButtonReleaseEvent", self.OnLeftButtonUp) + self.setup() + + def setup(self): + self.Internal = self.vtkInternal() + self.StartPosition = [None, None] + self.EndPosition = [None, None] + self.Moving = False + self.DrawPolygonPixels = True + self.PixelArray = vtk.vtkUnsignedCharArray() + + def DrawPolygonPixelsOn(self): + self.DrawPolygonPixels = True + + def DrawPolygonPixelsOff(self): + self.DrawPolygonPixels = False + + def GetPolygonPoints(self): + """ + Return the polygon points as a numpy array + NOTE: This function is not available in wrapped Python vtk + """ + return self.Internal.points + + def OnLeftButtonDown(self, obj, event): + """ + Left mouse button press event + """ + if self.interactor is None: return + + self.Moving = True + renWin = self.interactor.GetRenderWindow() + eventPos = self.interactor.GetEventPosition() + picker = vtk.vtkWorldPointPicker() + picker.Pick(eventPos[0], eventPos[1], 0, self.renderer) + position = list(picker.GetPickPosition()) + self.StartPosition[0], self.StartPosition[1], _ = position + self.EndPosition = self.StartPosition + + self.PixelArray.Initialize() + self.PixelArray.SetNumberOfComponents(3) + size = renWin.GetSize() + self.PixelArray.SetNumberOfTuples(size[0] * size[1]) + self.pixels = None + + # Note: In GetPixelData, must add 0 as 7th variable (in C++ it has a default + # value of 0, but not in Python) + renWin.GetPixelData(0, 0, size[0] - 1, size[1] - 1, 1, self.PixelArray, 0) + self.Internal.Clear() + self.Internal.AddPoint(self.StartPosition[0], self.StartPosition[1]) + self.InvokeEvent(vtk.vtk.vtkCommand.StartInteractionEvent) + + # Call parent function + # super().OnLeftButtonDown() + + def OnLeftButtonUp(self, obj, event): + """ + Left mouse button release event + When LMB is released, a EndPickEvent and EndInteractionEvent are emitted + NOTE: This is different to the C++ class, which emits a SelectionChangedEvent + instead of an EndPickEvent + """ + if self.interactor is None or not self.Moving: return + + if self.DrawPolygonPixels: + renWin = self.interactor.GetRenderWindow() + size = renWin.GetSize() + pixels = nps.vtk_to_numpy(self.PixelArray) + renWin.SetPixelData(0, 0, size[0] - 1, size[1] - 1, pixels.flatten(), 0, 0) + renWin.Frame() + + self.Moving = False + self.InvokeEvent(vtk.vtkCommand.SelectionChangedEvent) + self.InvokeEvent(vtk.vtkCommand.EndPickEvent) + self.InvokeEvent(vtk.vtkCommand.EndInteractionEvent) + self.renderer.RemoveActor(self.actor) + # Call parent function + # super().OnLeftButtonUp() + + def OnMouseMove(self, obj, event): + """ + On mouse move event + """ + if self.interactor is None or not self.Moving: return + + # Get lastest mouse position + eventPos = self.interactor.GetEventPosition() + picker = vtk.vtkWorldPointPicker() + picker.Pick(eventPos[0], eventPos[1], 0, self.renderer) + position = list(picker.GetPickPosition()) + self.EndPosition[0], self.EndPosition[1] = position[0], position[1] + # Update the polygon to include the lastest mouse position + lastPoint = self.Internal.GetPoint(self.Internal.GetNumberOfPoints() - 1) + newPoint = self.EndPosition + if np.linalg.norm(lastPoint - newPoint) > self.line_radius: + self.Internal.AddPoint(*newPoint) + if self.DrawPolygonPixels: + self.DrawPolygon() + + # Call parent function + # super().OnMouseMove() + + def DrawPolygon(self): + """ + Draw the polygon defined by the mouse move points + """ + if self.actor is not None: + self.renderer.RemoveActor(self.actor) + del self.actor + point_list = self.GetPolygonPoints() + vtk_polydata = vtk.vtkPolyData() + vtk_points = vtk.vtkPoints() + vtk_cell_array = vtk.vtkCellArray() + point_counter = 0 + for point_index in range(len(point_list)): + try: + line = [point_list[point_index], point_list[point_index + 1]] + except: + line = [point_list[point_index], point_list[0]] + vtk_lines = vtk.vtkLine() + vtk_lines.GetPointIds().SetNumberOfIds(len(line)) # make a quad + lines_counter = 0 + for point in line: + vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_lines.GetPointIds().SetId(lines_counter, point_counter) + lines_counter += 1 + point_counter += 1 + vtk_cell_array.InsertNextCell(vtk_lines) + vtk_polydata.SetPoints(vtk_points) + vtk_polydata.SetLines(vtk_cell_array) + tube_filter = vtk.vtkTubeFilter() + tube_filter.SetInputData(vtk_polydata) + tube_filter.SetRadius(self.line_radius) + tube_filter.SetNumberOfSides(50) + tube_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(tube_filter.GetOutputPort()) + mapper.Update() + self.actor = vtk.vtkActor() + self.actor.GetProperty().SetColor([0, 255, 0]) + self.actor.SetMapper(mapper) + self.renderer.AddActor(self.actor) + self.renderer.Render() + self.interactor.GetRenderWindow().Render() + + def SetDrawPolygonPixels(self, drawPolygonPixels): + self.DrawPolygonPixels = drawPolygonPixels + + def __str__(self): + """ + Replaces PrintSelf in C++ class + """ + indent = 2 * ' ' + s = super().__str__().rstrip() + '\n' + s += f'{indent}Moving : {self.Moving}\n' + s += f'{indent}DrawPolygonPixels: {self.DrawPolygonPixels}\n' + s += f'{indent}StartPosition: {self.StartPosition[0]}, {self.StartPosition[1]}\n' + s += f'{indent}EndPosition: {self.EndPosition[0]}, {self.EndPosition[1]}\n' + return s + + +def event_bla(*args, **kwargs): + interactor_style = args[0] + + +if __name__ == "__main__": + sphereSource = vtk.vtkSphereSource() + sphereSource.SetRadius(1) + sphereSource.Update() + mapper = vtk.vtkPolyDataMapper() + + mapper.SetInputConnection(sphereSource.GetOutputPort()) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + + renderer = vtk.vtkRenderer() + renderWindow = vtk.vtkRenderWindow() + renderWindow.AddRenderer(renderer) + renderWindowInteractor = vtk.vtkRenderWindowInteractor() + renderWindowInteractor.SetRenderWindow(renderWindow) + renderer.AddActor(actor) + renderer.SetBackground(1, 1, 1) + renderWindow.Render() + style = InteractorStyleDrawPolygon(interactor=renderWindowInteractor, renderer=renderer) + renderWindowInteractor.SetInteractorStyle(style) + style.AddObserver(vtk.vtkCommand.SelectionChangedEvent, event_bla) + renderWindowInteractor.Start() diff --git a/view/saasmi_visualization/vtk/saasmi_render_window.py b/view/saasmi_visualization/vtk/saasmi_render_window.py new file mode 100644 index 0000000..57f3b89 --- /dev/null +++ b/view/saasmi_visualization/vtk/saasmi_render_window.py @@ -0,0 +1,817 @@ +import logging + +import matplotlib.colors +import numpy as np +import vtk +from PyQt5 import QtWidgets, QtCore +from matplotlib import cm +from skimage import segmentation +from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor +from vtk.util import numpy_support + +from model_view import frontend_adapter +from view.saasmi_visualization.vtk import lasso_interactor + +vtk_colors = vtk.vtkNamedColors() +log = logging.getLogger(__name__) +FHI_COLORS = [ + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (1, 1, 1, 0.8), + (0.33596838, 0.18972332, 0.5256917, 0.8), + (0.0, 0.42687747, 0.71541502, 0.8), + (0.0, 0.63636364, 0.26086957, 0.8), + (1.0, 0.78656126, 0.0, 0.8), + (0.8972332, 0.11857708, 0.14229249, 0.8), + (0.69960474, 0.35968379, 0.2687747, 0.8), + (1.0, 0.0, 1.0, 0.8), +] +NODE_MAX_DEGREE = 10 + + +class MouseInteractorHighLightActor(QVTKRenderWindowInteractor): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + + def __init__(self, widget, parent=None): + super().__init__(widget) + self.AddObserver("LeftButtonPressEvent", self.leftButtonPressEvent) + self.AddObserver("RightButtonPressEvent", self.rightButtonPressEvent) + self.parent = parent + self.AddObserver(vtk.vtkCommand.KeyPressEvent, self.key_press_callback) + + def leftButtonPressEvent(self, obj, event): + clickPos = self.GetEventPosition() + picker = vtk.vtkPropPicker() + picker.Pick(clickPos[0], clickPos[1], 0, self.parent.graph_renderer) + real_image_picker = vtk.vtkPropPicker() + real_image_picker.Pick(clickPos[0], clickPos[1], 0, self.parent.real_image_renderer) + position = list(real_image_picker.GetPickPosition()) + if position != [0, 0, 0]: + self.left_click_position.emit(position) + + def rightButtonPressEvent(self, obj, event): + clickPos = self.GetEventPosition() + real_image_picker = vtk.vtkPropPicker() + real_image_picker.Pick(clickPos[0], clickPos[1], 0, self.parent.real_image_renderer) + position = list(real_image_picker.GetPickPosition()) + if position != [0, 0, 0]: + self.right_click_position.emit(position) + + def key_press_callback(self, *args, **kwargs): + if self.GetKeySym() == "Delete": + self.delete_button.emit() + + +class GraphVisualization(QtCore.QObject): + right_click_position = QtCore.pyqtSignal(list) + delete_button = QtCore.pyqtSignal() + left_click_position = QtCore.pyqtSignal(list) + lasso_selection = QtCore.pyqtSignal(list) + + def __init__(self, widget, model_view): + super().__init__() + self.model_view = model_view # type: frontend_adapter.FrontEndAdapter + self.graph_renderer = vtk.vtkRenderer() + self.graph_renderer.SetLayer(2) + self.labeled_image_renderer = vtk.vtkRenderer() + self.labeled_image_renderer.SetLayer(1) + self.real_image_renderer = vtk.vtkRenderer() + self.real_image_renderer.SetLayer(0) + self.network_atom_renderer = vtk.vtkRenderer() + self.network_atom_renderer.SetLayer(4) + self.network_structure_renderer = vtk.vtkRenderer() + self.network_structure_renderer.SetLayer(3) + self.analyser_image_mask_renderer = vtk.vtkRenderer() + self.analyser_image_mask_renderer.SetLayer(5) + self.mask = None + self.frame = QtWidgets.QFrame() + self.real_image_camera = self.real_image_renderer.GetActiveCamera() # type: vtk.vtkCamera + self.real_image_camera.Roll(270) + self.labeled_image_renderer.SetActiveCamera(self.real_image_camera) + self.graph_renderer.SetActiveCamera(self.real_image_camera) + self.network_atom_renderer.SetActiveCamera(self.real_image_camera) + self.network_structure_renderer.SetActiveCamera(self.real_image_camera) + self.analyser_image_mask_renderer.SetActiveCamera(self.real_image_camera) + if widget.layout() is None: + self.vl = QtWidgets.QVBoxLayout() + widget.setLayout(self.vl) + else: + self.vl = widget.layout() + for i in reversed(range(self.vl.count())): + self.vl.itemAt(i).widget().setParent(None) + self.set_up_graph_interactor() + self.vl.addWidget(self.interactor_widget) + widget.setLayout(self.vl) + self.render_window = self.interactor_widget.GetRenderWindow() + self.render_window.SetNumberOfLayers(6) + self.render_window.GetInteractor().Initialize() + self.render_window.GetInteractor().Start() + self.render_window.AddRenderer(self.real_image_renderer) + self.render_window.AddRenderer(self.graph_renderer) + self.render_window.AddRenderer(self.labeled_image_renderer) + self.render_window.AddRenderer(self.network_atom_renderer) + self.render_window.AddRenderer(self.network_structure_renderer) + self.render_window.AddRenderer(self.analyser_image_mask_renderer) + + def set_up_graph_interactor(self): + self.interactor_widget = MouseInteractorHighLightActor(self.frame, parent=self) + self.interactor_widget.left_click_position.connect(self.handle_left_click) + self.interactor_widget.right_click_position.connect(self.handle_right_click) + self.interactor_widget.delete_button.connect(self.handle_delete_press) + self.interactor_widget.SetInteractorStyle(vtk.vtkInteractorStyleImage()) + self.interactor_widget.GetInteractorStyle().SetDefaultRenderer(self.real_image_renderer) + + def handle_right_click(self, position): + self.right_click_position.emit(position) + + def handle_left_click(self, position): + self.left_click_position.emit(position) + + def handle_delete_press(self): + self.delete_button.emit() + + def set_background_color(self, background_color): + self.render_window.GetRenderers().GetFirstRenderer().SetBackground(background_color[:3]) + + def generate_edge_actor(self, image_number, centroid_dict, edge_radius, picked_key, apply_image_mask): + edge_list = [] + edges = self.model_view.get_saasmi_graph_edges(image_number, graph_type="rag", + apply_image_mask=apply_image_mask) + for edge in edges: + try: + if edge == picked_key: + continue + except ValueError: + pass + start_point = centroid_dict[edge[0]] + end_point = centroid_dict[edge[1]] + edge_list.append([start_point, end_point]) + + vtk_poly_data = self.generate_line_poly_data(edge_list) + tube_filter = vtk.vtkTubeFilter() + tube_filter.SetInputData(vtk_poly_data) + tube_filter.SetRadius(edge_radius) + tube_filter.SetNumberOfSides(50) + tube_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(tube_filter.GetOutputPort()) + mapper.Update() + actor = vtk.vtkActor() + actor.SetMapper(mapper) + return actor + + def generate_picked_edge_actor(self, edge, centroid_dict, radius): + try: + start_point = centroid_dict[edge[0]] + end_point = centroid_dict[edge[1]] + except KeyError: + return None + actor = self.get_tube_actor(start_point, end_point, radius) + self.set_actor_color(actor, "Red") + return actor + + def generate_picked_node_actor(self, position, node_radius): + vtk_poly_data = self.generate_poly_data_from_point_list([position]) + vtk_source = self.generate_sphere_vtk_source(node_radius) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + self.set_actor_color(actor, "Pink") + actor.SetMapper(mapper) + return actor + + def handle_lasso_selection(self, *args, **kwargs): + interactor_style = args[0] + polygon_points = interactor_style.GetPolygonPoints() + if len(polygon_points) > 2: + self.lasso_selection.emit(polygon_points) + + def change_interactor_style(self, interactor_style_str=None): + self.interactor_style = None + if interactor_style_str == "lasso_selection": + step_size = self.model_view.get_step_size() + line_radius = step_size[0] / 3 + self.interactor_style = lasso_interactor.InteractorStyleDrawPolygon(interactor=self.interactor_widget, + renderer=self.labeled_image_renderer, + line_radius=line_radius) + self.interactor_style.AddObserver(vtk.vtkCommand.SelectionChangedEvent, self.handle_lasso_selection) + + else: + self.interactor_style = vtk.vtkInteractorStyleImage() + self.interactor_widget.SetInteractorStyle(self.interactor_style) + self.interactor_widget.GetInteractorStyle().SetDefaultRenderer(self.labeled_image_renderer) + + def generate_source_node_actor(self, position, node_radius): + vtk_poly_data = self.generate_poly_data_from_point_list([position]) + vtk_source = self.generate_sphere_vtk_source(node_radius) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + self.set_actor_color(actor, "Orange") + actor.SetMapper(mapper) + return actor + + def generate_node_degree_actor_dict(self, image_number, centroid_dict, node_radius, picked_key, source_node): + node_degree_point_dict = {} + degree_dict = self.model_view.get_saasmi_node_degree_dict(image_number=image_number, graph_type="rag") + for key, centroid in centroid_dict.items(): + if isinstance(picked_key, int) and (key == picked_key or key == source_node): + continue + node_degree = degree_dict[key] + if node_degree in node_degree_point_dict: + node_degree_point_dict[node_degree].append(centroid) + else: + node_degree_point_dict[node_degree] = [centroid] + node_degree_poly_data_dict = {} + for key, centroid_list in node_degree_point_dict.items(): + node_degree_poly_data_dict[key] = self.generate_poly_data_from_point_list(centroid_list) + node_degree_actor_dict = {} + for key, vtk_poly_data in node_degree_poly_data_dict.items(): + vtk_source = self.generate_sphere_vtk_source(node_radius) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + node_degree_actor_dict[key] = actor + return node_degree_actor_dict + + def render_graph(self, image_number, graph_settings, picked_key, source_node, apply_image_mask): + node_radius = graph_settings["node_radius"] * self.radius_multiplier + edge_radius = graph_settings["edge_radius"] * self.radius_multiplier + full_centroid_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number=image_number, + graph_type="rag", + apply_image_mask=False) # type: dict + image_mask_centroid_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number=image_number, + graph_type="rag", + apply_image_mask=apply_image_mask) # type: dict + degree_actor_dict = self.generate_node_degree_actor_dict(image_number, image_mask_centroid_dict, node_radius, + picked_key, source_node) + node_color_map = self.model_view.get_config_by_key_sub_key("saasmi", "node_color_map") + color_list = self.generate_color_list_from_cmap(node_color_map, max_degree=NODE_MAX_DEGREE) + for degree, actor in degree_actor_dict.items(): + try: + color = color_list[degree][:3] + except IndexError: + color = (1, 1, 1) + actor.GetProperty().SetColor(*color) + self.graph_renderer.AddActor(actor) + edge_actor = self.generate_edge_actor(image_number, full_centroid_dict, edge_radius, picked_key, + apply_image_mask) + edge_actor.GetProperty().SetColor(0.5, 0.5, 1) + self.graph_renderer.AddActor(edge_actor) + if source_node in image_mask_centroid_dict: + source_node_actor = self.generate_source_node_actor(full_centroid_dict[source_node], node_radius) + self.graph_renderer.AddActor(source_node_actor) + if picked_key in image_mask_centroid_dict: + self.picked_node = picked_key + picked_node_actor = self.generate_picked_node_actor(full_centroid_dict[picked_key], node_radius) + self.graph_renderer.AddActor(picked_node_actor) + elif picked_key is not None: + picked_edge_actor = self.generate_picked_edge_actor(picked_key, full_centroid_dict, edge_radius) + if picked_edge_actor is not None: + self.graph_renderer.AddActor(picked_edge_actor) + + def render(self, image_number, beta, disk_size, picked_key=None, source_node=None, scan_index=None, + label_image_opacity=1, + graph_settings=None, network_settings=None, render_analyse_area_mask=False, apply_image_mask=True, + auto_generate_rag=True): + ############################ + # Workaround for VTK Bug where renderer lose the render window + self.real_image_renderer.SetRenderWindow(self.render_window) + self.graph_renderer.SetRenderWindow(self.render_window) + self.labeled_image_renderer.SetRenderWindow(self.render_window) + self.network_atom_renderer.SetRenderWindow(self.render_window) + self.network_structure_renderer.SetRenderWindow(self.render_window) + self.analyser_image_mask_renderer.SetRenderWindow(self.render_window) + ########################### + self.node_actor_dict = {} + self.edge_actor_dict = {} + self.graph_renderer.RemoveAllViewProps() + self.real_image_renderer.RemoveAllViewProps() + self.network_atom_renderer.RemoveAllViewProps() + self.network_structure_renderer.RemoveAllViewProps() + self.analyser_image_mask_renderer.RemoveAllViewProps() + rag, labels = self.model_view.get_saasmi_rag_and_label(image_number, beta, scan_index=scan_index, + auto_generate=auto_generate_rag, disk_size=disk_size) + self.radius_multiplier = self.model_view.get_step_size()[0] + + self.picked_node = None + if graph_settings["show_graph"]: + self.render_graph(image_number=image_number, graph_settings=graph_settings, picked_key=picked_key, + source_node=source_node, apply_image_mask=apply_image_mask) + self.update_labeled_image_renderer(rag, labels, label_image_opacity) + if network_settings["show_atoms"]: + self.render_network_atoms(image_number=image_number, network_settings=network_settings, + apply_image_mask=apply_image_mask, picked_key=picked_key) + if network_settings["show_network_structure"]: + self.render_network_structure(image_number=image_number, network_settings=network_settings, + apply_image_mask=apply_image_mask, picked_key=picked_key) + self.update_real_image_renderer(scan_index=scan_index, image_number=image_number) + if render_analyse_area_mask is True: + self.render_analyse_area_image_mask(image_number) + self.update() + + def render_analyse_area_image_mask(self, image_number): + self.analyser_image_mask_renderer.RemoveAllViewProps() + image = self.model_view.get_analyse_area_mask_image(image_number) + image = np.fliplr(np.rot90(image, -1)) + vtk_array = numpy_support.numpy_to_vtk( + num_array=image.ravel(), + deep=True, + array_type=vtk.VTK_FLOAT) + vtk_image_data = vtk.vtkImageData() + dimensions = image.shape + vtk_image_data.SetDimensions(dimensions[1], dimensions[0], 1) + vtk_image_data.AllocateScalars(vtk.VTK_DOUBLE, 1) + vtk_image_data.GetPointData().SetScalars(vtk_array) + step_size = self.model_view.get_step_size() + vtk_image_data.SetSpacing(step_size[0], step_size[1], 1) + # Setting up a greyscale lookup table + image_data_geometry_filter = vtk.vtkImageDataGeometryFilter() + image_data_geometry_filter.SetInputData(vtk_image_data) + image_data_geometry_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(image_data_geometry_filter.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetOpacity(0.3) + mapper.Update() + actor.SetMapper(mapper) + self.analyser_image_mask_renderer.AddActor(actor) + + def generate_sphere_vtk_source(self, radius): + sphere_source = vtk.vtkSphereSource() + sphere_source.SetRadius(radius) + sphere_source.Update() + return sphere_source + + def generate_poly_data_from_point_list(self, point_list): + vtk_points = vtk.vtkPoints() + for point in point_list: + vtk_points.InsertNextPoint(point[0], point[1], 0) + vtk_poly_data = vtk.vtkPolyData() + vtk_poly_data.SetPoints(vtk_points) + return vtk_poly_data + + def get_silicon_actor(self, image_number, node_radius, apply_image_mask, picked_key=None): + silicon_positions_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number=image_number, + graph_type="silicon", + apply_image_mask=apply_image_mask) + if picked_key in silicon_positions_dict: + position = silicon_positions_dict.pop(picked_key) + picked_key_actor = self.generate_picked_node_actor(position, + node_radius=node_radius) + self.network_atom_renderer.AddActor(picked_key_actor) + + silicon_positions = list(silicon_positions_dict.values()) + vtk_source = self.generate_sphere_vtk_source(node_radius) + vtk_poly_data = self.generate_poly_data_from_point_list(silicon_positions) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + + # Visualize + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_2_color") + color = [c / 255 for c in color] + actor.GetProperty().SetColor(*color) + actor.SetMapper(mapper) + return actor + + def get_oxygen_actor(self, image_number, node_radius, apply_image_mask, picked_key=None): + oxygen_positions_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number=image_number, + graph_type="oxygen", + apply_image_mask=apply_image_mask) + if picked_key in oxygen_positions_dict: + position = oxygen_positions_dict.pop(picked_key) + actor = self.generate_picked_node_actor(position, node_radius) + self.network_atom_renderer.AddActor(actor) + oxygen_positions = list(oxygen_positions_dict.values()) + vtk_source = self.generate_sphere_vtk_source(node_radius) + vtk_poly_data = self.generate_poly_data_from_point_list(oxygen_positions) + glyph_3d = vtk.vtkGlyph3D() + glyph_3d.SetSourceConnection(vtk_source.GetOutputPort()) + glyph_3d.SetInputData(vtk_poly_data) + glyph_3d.Update() + + # Visualize + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(glyph_3d.GetOutputPort()) + actor = vtk.vtkActor() + color = self.model_view.get_config_by_key_sub_key("saasmi", "network_atom_1_color") + color = [c / 255 for c in color] + actor.GetProperty().SetColor(*color) + actor.SetMapper(mapper) + return actor + + def render_network_atoms(self, image_number, network_settings, apply_image_mask, picked_key): + self.network_atom_renderer.RemoveAllViewProps() + silicon_radius = network_settings["atom_2_radius"] * self.radius_multiplier + silicon_actor = self.get_silicon_actor(image_number=image_number, node_radius=silicon_radius, + apply_image_mask=apply_image_mask, picked_key=picked_key) + oxygen_radius = network_settings["atom_1_radius"] * self.radius_multiplier + oxygen_actor = self.get_oxygen_actor(image_number=image_number, node_radius=oxygen_radius, + apply_image_mask=apply_image_mask, picked_key=picked_key) + self.network_atom_renderer.AddActor(silicon_actor) + self.network_atom_renderer.AddActor(oxygen_actor) + self.network_atom_renderer.Render() + + def render_network_structure(self, image_number, network_settings, apply_image_mask, picked_key): + tube_radius = network_settings["ring_thickness"] * self.radius_multiplier + ring_type = network_settings["ring_type"] + self.network_structure_renderer.RemoveAllViewProps() + network_structure_actor = self.get_network_ring_structure_actor(image_number, tube_radius, ring_type=ring_type, + apply_image_mask=apply_image_mask, + picked_key=picked_key) + self.network_structure_renderer.AddActor(network_structure_actor) + self.network_structure_renderer.Render() + + def set_actor_color(self, actor, color): + if isinstance(color, str): + color = vtk_colors.GetColor3d(color) + actor.GetProperty().SetColor(color) + actor.GetProperty().SetDiffuse(1.0) + actor.GetProperty().SetSpecular(0.0) + + def get_tube_actor(self, start_point, end_point, radius=2, color=None): + line_source = vtk.vtkLineSource() + line_source.SetPoint1(start_point[0], start_point[1], 0) + line_source.SetPoint2(end_point[0], end_point[1], 0) + tube_filter = vtk.vtkTubeFilter() + tube_filter.SetInputConnection(line_source.GetOutputPort()) + tube_filter.SetRadius(radius) + tube_filter.SetNumberOfSides(50) + tube_filter.Update() + + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(tube_filter.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + if color is not None: + self.set_actor_color(actor, color) + return actor + + def update_labeled_image_renderer(self, rag, labels, label_image_opacity=1): + self.labeled_image_renderer.RemoveAllViewProps() + if labels is None: + return + degree_labels = self.colorize_degrees(rag, labels) + image_label_overlay = self.generate_color_image_by_labels(self.model_view, degree_labels) + border_color = self.model_view.get_config_by_key_sub_key("saasmi", "border_color") + image_label_overlay = segmentation.mark_boundaries(image_label_overlay, labels, color=border_color) + image_label_overlay = image_label_overlay.astype(np.uint8) + image_label_overlay = self.highlight_picked_area(image_label_overlay, labels) + vtk_image_data = self.numpy_array_as_vtk_image_data(image_label_overlay) + step_size = self.model_view.get_step_size() + vtk_image_data.SetSpacing(step_size[0], step_size[1], 1) + image_data_geometry_filter = vtk.vtkImageDataGeometryFilter() + image_data_geometry_filter.SetInputData(vtk_image_data) + image_data_geometry_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(image_data_geometry_filter.GetOutputPort()) + mapper.Update() + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetOpacity(label_image_opacity) + self.labeled_image_renderer.AddActor(actor) + self.labeled_image_renderer.Render() + + def highlight_picked_area(self, image: np.array, labels: np.array): + if self.picked_node is not None: + mask = labels == self.picked_node + image[mask, 3] = 255 + image = image.astype(np.uint8) + return image + else: + return image + + def reset_render_window(self): + self.graph_renderer.RemoveAllViewProps() + + def update(self): + self.render_window.Render() + + def reset_render_camera(self): + self.real_image_renderer.ResetCamera() + + @staticmethod + def colorize_degrees(rag, label_image): + """Colorize labels according to the degree of the corresponding node.""" + labels_degree_dict = GraphVisualization.node_degree_dict(rag, label_image) + for label in GraphVisualization.outer_labels(label_image): + labels_degree_dict[label] = -1 + for label in np.unique(label_image): + if label not in labels_degree_dict: + label_image[label_image == label] = 0 + degree_labels = np.vectorize(labels_degree_dict.__getitem__)(label_image) + return degree_labels + + @staticmethod + def node_degree_dict(rag, label_image): + """Return dictionary with nodes as keys and degrees as values. + + Return a dictionary with the nodes of the rag that are in the interior + as keys and the degree of that nodes as values. + """ + node_degree_map = { + key: value + for (key, value) in rag.degree + if key not in GraphVisualization.outer_labels(label_image) + } + return node_degree_map + + @staticmethod + def outer_labels(labels): + """Return a set with labels of the interior.""" + labels = set( + np.unique( + np.concatenate( + (labels[0, :], labels[-1, :], labels[:, 0], labels[:, -1]) + ) + ) + ) + return labels + + @staticmethod + def numpy_array_as_vtk_image_data(source_numpy_array): + """ + :param source_numpy_array: source array with 2-3 dimensions. If used, the third dimension represents the channel count. + Note: Channels are flipped, i.e. source is assumed to be BGR instead of RGB (which works if you're using cv2.imread function to read three-channel images) + Note: Assumes array value at [0,0] represents the upper-left pixel. + :type source_numpy_array: np.ndarray + :return: vtk-compatible image, if conversion is successful. Raises exception otherwise + :rtype vtk.vtkImageData + """ + + if len(source_numpy_array.shape) > 2: + channel_count = source_numpy_array.shape[2] + else: + channel_count = 1 + + output_vtk_image = vtk.vtkImageData() + output_vtk_image.SetDimensions(source_numpy_array.shape[0], source_numpy_array.shape[1], 1) + + vtk_type_by_numpy_type = { + np.uint8: vtk.VTK_UNSIGNED_CHAR, + np.uint16: vtk.VTK_UNSIGNED_SHORT, + np.uint32: vtk.VTK_UNSIGNED_INT, + np.uint64: vtk.VTK_UNSIGNED_LONG if vtk.VTK_SIZEOF_LONG == 64 else vtk.VTK_UNSIGNED_LONG_LONG, + np.int8: vtk.VTK_CHAR, + np.int16: vtk.VTK_SHORT, + np.int32: vtk.VTK_INT, + np.int64: vtk.VTK_LONG if vtk.VTK_SIZEOF_LONG == 64 else vtk.VTK_LONG_LONG, + np.float32: vtk.VTK_FLOAT, + np.float64: vtk.VTK_DOUBLE + } + vtk_datatype = vtk_type_by_numpy_type[source_numpy_array.dtype.type] + + # Note: don't flip (take out next two lines) if input is RGB. + # Likewise, BGRA->RGBA would require a different reordering here. + + depth_array = numpy_support.numpy_to_vtk(source_numpy_array.reshape((-1, 4), order='F'), deep=True, + array_type=vtk_datatype) + depth_array.SetNumberOfComponents(channel_count) + output_vtk_image.GetPointData().SetScalars(depth_array) + + output_vtk_image.Modified() + return output_vtk_image + + + @staticmethod + def generate_color_list_from_cmap(cmap_name, max_degree=NODE_MAX_DEGREE): + if cmap_name == "FHI-Color-Map": + return GraphVisualization.generate_fhi_color_list() + cmap = cm.get_cmap(cmap_name) + if hasattr(cmap, "colors"): + colors_list = list(cmap.colors) + else: + colors_list = cmap(np.arange(0, cmap.N)) + number_of_colors = len(colors_list) + color_step_size = int(number_of_colors / max_degree) + if color_step_size < 1: + color_step_size = 1 + new_color_list = [] + for i, loop_color in enumerate(colors_list): + if i % color_step_size == 0: + new_color_list.append(loop_color) + return new_color_list + + @staticmethod + def generate_fhi_color_list(max_degree=NODE_MAX_DEGREE): + try: + return FHI_COLORS[0:max_degree + 1] + except IndexError: + log.info("The number of degrees exceed the number of colors in the fhi color list") + return FHI_COLORS + + @staticmethod + def generate_vtk_color_list_from_float_color_list(color_list): + new_color_list = [] + for loop_color in color_list: + try: + r, g, b, a = loop_color + except ValueError: + r, g, b = loop_color + a = 0.50196 + color_add = [int(r * 255), int(g * 255), int(b * 255), int(a * 255)] + new_color_list.append(color_add) + return new_color_list + + @staticmethod + def generate_color_image_by_labels(model_view, degree_labels): + rag_color_map = model_view.get_config_by_key_sub_key("saasmi", "rag_color_map") + if rag_color_map == "FHI-Color-Map": + new_color_list = GraphVisualization.generate_fhi_color_list() + else: + new_color_list = GraphVisualization.generate_color_list_from_cmap(rag_color_map) + new_color_list = GraphVisualization.generate_vtk_color_list_from_float_color_list(new_color_list) + image_label_overlay = GraphVisualization.label2rgba(degree_labels, new_color_list, bg_label=-1) + # image_label_overlay = color.label2rgb(degree_labels, colors=new_color_list, bg_label=0, + # bg_color=(100, 100, 100)) + return image_label_overlay + + @staticmethod + def label2rgba(labels, colors, bg_label=-1, bg_color=(0, 0, 0, 128)): + image_label_overlay = np.zeros((*labels.shape, 4)) + label_list = np.unique(labels) + for i in label_list: + if i == bg_label: + color = bg_color + else: + try: + color = colors[i] + except IndexError: + color = [140, 140, 140, 128] + mask = labels == i + for c in range(len(color)): + image_label_overlay[mask, c] = color[c] + return image_label_overlay + + def update_real_image_renderer(self, scan_index, image_number): + image = self.model_view.get_2d_image_by_number(number=image_number, scan_index=scan_index) + image = np.fliplr(np.rot90(image, -1)) + contrast_settings = self.model_view.get_contrast_settings(scan_index) + nan_color = self.model_view.get_nan_color(scan_index) + step_size = self.model_view.get_step_size(scan_index) + vtk_array = numpy_support.numpy_to_vtk( + num_array=image.ravel(), + deep=True, + array_type=vtk.VTK_FLOAT) + min_z, max_z = np.nanmin(image), np.nanmax(image) + vtk_image_data = vtk.vtkImageData() + dimensions = image.shape + vtk_image_data.SetDimensions(dimensions[1], dimensions[0], 1) + vtk_image_data.AllocateScalars(vtk.VTK_DOUBLE, 1) + vtk_image_data.SetSpacing(step_size[0], step_size[1], 1) + vtk_image_data.GetPointData().SetScalars(vtk_array) + # Setting up a greyscale lookup table + grey_scale_color_table = vtk.vtkLookupTable() + grey_scale_color_table.SetSaturationRange(0, 0) + + if contrast_settings[0] == "percentile": + if np.isnan(image).all(): + percentile = (0, 0) + else: + percentile = np.percentile(image[~np.isnan(image)], (contrast_settings[1], contrast_settings[2])) + grey_scale_color_table.SetRange(percentile[0], percentile[1]) + elif contrast_settings[0] == "absolute": + grey_scale_color_table.SetRange(contrast_settings[1], contrast_settings[2]) + else: + grey_scale_color_table.SetRange(min_z, max_z) + grey_scale_color_table.SetBelowRangeColor(0, 1, 1, 1) + grey_scale_color_table.SetAboveRangeColor(1, 0, 1, 1) + grey_scale_color_table.SetUseAboveRangeColor(0) + grey_scale_color_table.SetUseBelowRangeColor(0) + grey_scale_color_table.SetHueRange(0, 0) + grey_scale_color_table.SetValueRange(0, 1) + grey_scale_color_table.SetNanColor(nan_color) + image_data_geometry_filter = vtk.vtkImageDataGeometryFilter() + image_data_geometry_filter.SetInputData(vtk_image_data) + image_data_geometry_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetLookupTable(grey_scale_color_table) + mapper.SetUseLookupTableScalarRange(1) + mapper.SetInputConnection(image_data_geometry_filter.GetOutputPort()) + actor = vtk.vtkActor() + actor.SetMapper(mapper) + grey_scale_color_table.Build() + mapper.Update() + self.real_image_renderer.AddActor(actor) + + def export_as_image(self, resolution, file_path): + vtk_render_window = vtk.vtkRenderWindow() + vtk_render_window.SetNumberOfLayers(5) + vtk_render_window.SetUseOffScreenBuffers(1) + vtk_render_window.SetOffScreenRendering(1) + vtk_render_window.AddRenderer(self.real_image_renderer) + vtk_render_window.AddRenderer(self.graph_renderer) + vtk_render_window.AddRenderer(self.labeled_image_renderer) + vtk_render_window.AddRenderer(self.network_atom_renderer) + vtk_render_window.AddRenderer(self.network_structure_renderer) + vtk_render_window.AddRenderer(self.analyser_image_mask_renderer) + vtk_render_window.SetSize(resolution[0], resolution[1]) + vtk_window_to_image_filter = vtk.vtkWindowToImageFilter() + vtk_window_to_image_filter.SetInput(vtk_render_window) + vtk_window_to_image_filter.SetInputBufferTypeToRGB() + vtk_window_to_image_filter.ReadFrontBufferOff() + vtk_window_to_image_filter.Update() + writer = vtk.vtkPNGWriter() + writer.SetInputConnection(vtk_window_to_image_filter.GetOutputPort()) + if file_path.split(".")[-1] != "png": + file_path += ".png" + writer.SetFileName(file_path) + vtk_window_to_image_filter.Modified() + writer.Write() + vtk_render_window.SetOffScreenRendering(0) + + def generate_line_poly_data(self, line_list): + vtk_polydata = vtk.vtkPolyData() + vtk_points = vtk.vtkPoints() + vtk_lines = vtk.vtkCellArray() + point_counter = 0 + for line in line_list: + lines = vtk.vtkLine() + lines.GetPointIds().SetNumberOfIds(len(line)) # make a quad + lines_counter = 0 + for point in line: + vtk_points.InsertNextPoint(point[0], point[1], 0) + lines.GetPointIds().SetId(lines_counter, point_counter) + lines_counter += 1 + point_counter += 1 + vtk_lines.InsertNextCell(lines) + vtk_polydata.SetPoints(vtk_points) + vtk_polydata.SetLines(vtk_lines) + return vtk_polydata + + def generate_ring_poly_data(self, edges): + vtk_polydata = vtk.vtkPolyData() + vtk_points = vtk.vtkPoints() + vtk_line_array = vtk.vtkCellArray() + point_counter = 0 + for edge in edges: + line = vtk.vtkLine() + for point in edge: + vtk_points.InsertNextPoint(point[0], point[1], 0) + point_counter += 1 + + line.GetPointIds().SetId(0, point_counter - 2) + line.GetPointIds().SetId(1, point_counter - 1) + vtk_line_array.InsertNextCell(line) + + vtk_polydata.SetPoints(vtk_points) + vtk_polydata.SetLines(vtk_line_array) + return vtk_polydata + + def get_network_ring_structure_actor(self, image_number, tube_radius, ring_type=None, apply_image_mask=False, + picked_key=None): + edge_id_list = self.model_view.get_saasmi_graph_edges(image_number, graph_type=ring_type, + apply_image_mask=apply_image_mask) + edges = [] + node_dict = self.model_view.get_saasmi_graph_node_position_dict(image_number, graph_type=ring_type, + apply_image_mask=False) + try: + if picked_key in edge_id_list: + picked_key_actor = self.generate_picked_edge_actor(picked_key, node_dict, tube_radius) + self.network_structure_renderer.AddActor(picked_key_actor) + edge_id_list.remove(picked_key) + except: + pass + for edge in edge_id_list: + new_points = [] + for point in edge: + new_points.append(list(node_dict[point])) + edges.append(new_points) + vtk_polygon_polydata = self.generate_ring_poly_data(edges) + # Visualize + tube_filter = vtk.vtkTubeFilter() + tube_filter.SetInputData(vtk_polygon_polydata) + tube_filter.SetRadius(tube_radius) + tube_filter.SetNumberOfSides(50) + tube_filter.Update() + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(tube_filter.GetOutputPort()) + mapper.Update() + actor = vtk.vtkActor() + color = [0, 0, 1] + actor.GetProperty().SetColor(*color) + actor.SetMapper(mapper) + return actor + diff --git a/view/settings_dialog.py b/view/settings_dialog.py new file mode 100644 index 0000000..e53fd40 --- /dev/null +++ b/view/settings_dialog.py @@ -0,0 +1,130 @@ +import logging + +from PyQt5 import QtWidgets + +from view.ui import ui_dialog_settings + +log = logging.getLogger(__name__) + + +class SettingsDialog(QtWidgets.QDialog): + def __init__(self, model_view, current_image_number, parent=None): + self.model_view = model_view + self.image_number = current_image_number + QtWidgets.QDialog.__init__(self, parent) + self.ui_dialog = ui_dialog_settings.Ui_DialogSettings() + self.ui_dialog.setupUi(self) + self.ui_dialog.tabWidget_scan.setCurrentIndex(0) + self.set_up_program_settings_dialog() + self.set_up_time_stamps_settings_dialog() + self.ui_dialog.pushButton_color_picker.clicked.connect(self.colorpicker_dialog_handler) + + def set_up_time_stamps_settings_dialog(self): + self.ui_dialog.checkBox_timestamps_show.setChecked(self.get_time_stamp_config_by_key('show_time_stamps')) + self.ui_dialog.checkBox_show_scan_direction.setChecked(self.get_time_stamp_config_by_key('show_scan_direction')) + self.ui_dialog.comboBox_timestamps_unit.setCurrentText(self.get_time_stamp_config_by_key('unit')) + self.ui_dialog.spinBox_timestamps_red.setValue(self.get_time_stamp_config_by_key('color')['red']) + self.ui_dialog.spinBox_timestamps_green.setValue(self.get_time_stamp_config_by_key('color')['green']) + self.ui_dialog.spinBox_timestamps_blue.setValue(self.get_time_stamp_config_by_key('color')['blue']) + self.ui_dialog.spinBox_timestamps_pre_decimals.setValue( + self.get_time_stamp_config_by_key('digits_before')) + self.ui_dialog.spinBox_timestamps_decimals.setValue(self.get_time_stamp_config_by_key('digits_after')) + self.ui_dialog.spinBox_timestamps_text_font_size.setValue(self.get_time_stamp_config_by_key('font_size')) + self.ui_dialog.spinBox_timestamps_position_x.setValue(self.get_time_stamp_config_by_key('position')[0]) + self.ui_dialog.spinBox_timestamps_position_y.setValue(self.get_time_stamp_config_by_key('position')[1]) + self.ui_dialog.doubleSpinBox_start_time.setValue(self.get_time_stamp_config_by_key('start_time')) + self.ui_dialog.pushButton_start_time_to_current_frame.clicked.connect(self.set_start_time_to_current_frame) + + def set_up_program_settings_dialog(self): + self.ui_dialog.checkBox_matplotlib_toolbar.setChecked(self.get_program_config_by_key('matplotlib_toolbar')) + self.ui_dialog.checkBox_show_second_window.setChecked(self.get_program_config_by_key('show_second_window')) + self.ui_dialog.comboBox_main_render_window.setCurrentText( + self.get_program_config_by_key('main_render_window')) + self.ui_dialog.comboBox_second_render_window.setCurrentText( + self.get_program_config_by_key('second_render_window')) + self.ui_dialog.comboBox_2d_filter_image.setCurrentText(self.get_program_config_by_key('2d_filter_image')) + self.ui_dialog.comboBox_2d_filter_fft.setCurrentText(self.get_program_config_by_key('2d_filter_fft')) + self.ui_dialog.comboBox_1d_filter_image.setCurrentText(self.get_program_config_by_key('1d_filter_image')) + self.ui_dialog.comboBox_1d_filter_signal.setCurrentText(self.get_program_config_by_key('1d_filter_signal')) + self.ui_dialog.comboBox_1d_filter_fft.setCurrentText(self.get_program_config_by_key('1d_filter_fft')) + + self.ui_dialog.checkBox_program_1d_multiprocessing.setChecked( + self.get_program_config_by_key('filter_1d_multiprocessing')) + self.ui_dialog.spinBox_program_1d_concurrency.setValue( + self.get_program_config_by_key('filter_1d_concurrency')) + self.ui_dialog.checkBox_program_2d_multiprocessing.setChecked( + self.get_program_config_by_key('filter_2d_multiprocessing')) + self.ui_dialog.spinBox_program_2d_concurrency.setValue( + self.get_program_config_by_key('filter_2d_concurrency')) + + def colorpicker_dialog_handler(self): + color_dialog = QtWidgets.QColorDialog() + color = color_dialog.getColor(options=QtWidgets.QColorDialog.DontUseNativeDialog) + if color is not None: + r, g, b, a = color.getRgb() + self.ui_dialog.spinBox_timestamps_red.setValue(r) + self.ui_dialog.spinBox_timestamps_green.setValue(g) + self.ui_dialog.spinBox_timestamps_blue.setValue(b) + + def accept(self): + color_dict = {'red': self.ui_dialog.spinBox_timestamps_red.value(), + 'green': self.ui_dialog.spinBox_timestamps_green.value(), + 'blue': self.ui_dialog.spinBox_timestamps_blue.value()} + # TIME STAMPS + self.set_time_stamp_config_by_key('color', color_dict) + self.set_time_stamp_config_by_key('font_size', self.ui_dialog.spinBox_timestamps_text_font_size.value()) + self.set_time_stamp_config_by_key('digits_after', self.ui_dialog.spinBox_timestamps_decimals.value()) + self.set_time_stamp_config_by_key('digits_before', self.ui_dialog.spinBox_timestamps_pre_decimals.value()) + self.set_time_stamp_config_by_key('show_time_stamps', self.ui_dialog.checkBox_timestamps_show.isChecked()) + self.set_time_stamp_config_by_key('show_scan_direction', + self.ui_dialog.checkBox_show_scan_direction.isChecked()) + self.set_time_stamp_config_by_key('unit', self.ui_dialog.comboBox_timestamps_unit.currentText()) + self.set_time_stamp_config_by_key('position', + [self.ui_dialog.spinBox_timestamps_position_x.value(), + self.ui_dialog.spinBox_timestamps_position_y.value()]) + self.set_time_stamp_config_by_key('start_time', self.ui_dialog.doubleSpinBox_start_time.value()) + # PROGRAM SETTINGS + self.set_program_config_by_key('filter_1d_multiprocessing', + self.ui_dialog.checkBox_program_1d_multiprocessing.isChecked()) + self.set_program_config_by_key('filter_1d_concurrency', + self.ui_dialog.spinBox_program_1d_concurrency.value()) + self.set_program_config_by_key('filter_2d_multiprocessing', + self.ui_dialog.checkBox_program_2d_multiprocessing.isChecked()) + self.set_program_config_by_key('filter_2d_concurrency', + self.ui_dialog.spinBox_program_2d_concurrency.value()) + self.set_program_config_by_key('main_render_window', + self.ui_dialog.comboBox_main_render_window.currentText()) + self.set_program_config_by_key('second_render_window', + self.ui_dialog.comboBox_second_render_window.currentText()) + self.set_program_config_by_key('2d_filter_image', self.ui_dialog.comboBox_2d_filter_image.currentText()) + self.set_program_config_by_key('2d_filter_fft', self.ui_dialog.comboBox_2d_filter_fft.currentText()) + self.set_program_config_by_key('1d_filter_image', self.ui_dialog.comboBox_1d_filter_image.currentText()) + self.set_program_config_by_key('1d_filter_signal', self.ui_dialog.comboBox_1d_filter_signal.currentText()) + self.set_program_config_by_key('1d_filter_fft', self.ui_dialog.comboBox_1d_filter_fft.currentText()) + self.set_program_config_by_key('matplotlib_toolbar', self.ui_dialog.checkBox_matplotlib_toolbar.isChecked()) + self.set_program_config_by_key('show_second_window', self.ui_dialog.checkBox_show_second_window.isChecked()) + self.done(QtWidgets.QDialog.Accepted) + + def set_start_time_to_current_frame(self): + start_time = self.model_view.get_time_stamp_by_frame(image_number=self.image_number, + unit=self.ui_dialog.comboBox_timestamps_unit.currentText()) + start_time = round(start_time, self.ui_dialog.spinBox_timestamps_decimals.value()) + self.ui_dialog.doubleSpinBox_start_time.setValue(start_time) + + def get_time_stamp_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('time_stamps', key) + + def set_time_stamp_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('time_stamps', key, value) + + def get_program_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('program_settings', key) + + def set_program_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('program_settings', key, value) + + def get_feature_settings_config_by_key(self, key): + return self.model_view.get_config_by_key_sub_key('feature_settings', key) + + def set_feature_settings_config_by_key(self, key, value): + return self.model_view.set_config_by_key_sub_key('feature_settings', key, value) diff --git a/view/string_formatting/__init__.py b/view/string_formatting/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/view/string_formatting/__init__.py @@ -0,0 +1 @@ + diff --git a/view/string_formatting/math_text.py b/view/string_formatting/math_text.py new file mode 100644 index 0000000..63d45cc --- /dev/null +++ b/view/string_formatting/math_text.py @@ -0,0 +1,30 @@ + + +from math import floor, log10 + + +def sci_notation(num, decimal_digits=1,force_sign=False, precision=None, exponent=None, as_string=True): + """ + Returns a string representation of the scientific + notation of the given number formatted for use with + LaTeX or Mathtext, with specified number of significant + decimal digits and precision (number of decimal digits + to show). The exponent to be used can also be specified + explicitly. + STOLEN FROM: https://stackoverflow.com/a/18313780/12800229 + """ + if exponent is None: + exponent = int(floor(log10(abs(num)))) + coeff = round(num / float(10**exponent), decimal_digits) + if precision is None: + precision = decimal_digits + if not as_string: + if force_sign: + return r"${0:+.{2}f}\cdot10^{{{1:d}}}$".format(coeff, exponent, precision) + else: + return r"${0:.{2}f}\cdot10^{{{1:d}}}$".format(coeff, exponent, precision) + else: + if force_sign: + return r"{0:+.{2}f}\cdot10^{{{1:d}}}".format(coeff, exponent, precision) + else: + return r"{0:.{2}f}\cdot10^{{{1:d}}}".format(coeff, exponent, precision) \ No newline at end of file diff --git a/view/ui/DialogAnalyse.ui b/view/ui/DialogAnalyse.ui new file mode 100644 index 0000000..0bc4f00 --- /dev/null +++ b/view/ui/DialogAnalyse.ui @@ -0,0 +1,248 @@ + + + Dialog + + + + 0 + 0 + 1015 + 828 + + + + Dialog + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Analyse Window + + + + + + + + + + + + + + 0 + 0 + + + + + + + Feature + + + + + + Feature Class + + + + + + + + + + Feature + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Settings + + + + + + The threshold when a movement is detected as a jump + + + Jump Threshold: + + + + + + + The threshold when a movement is detected as a jump + + + + + + + Get equal number of Jumps for all feature classes + + + + + + + + + + <html><head/><body><p>Sometimes feautres jump out of the scan or new feature appear in the scan. If this option is checked they will be counted as jump.</p></body></html> + + + Count Jumps not inside of the scan + + + + + + + + + + Image + + + + + + + + + From Image + + + + + + + + + + to + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + + + + diff --git a/view/ui/DialogAverageImage.ui b/view/ui/DialogAverageImage.ui new file mode 100644 index 0000000..2b1b585 --- /dev/null +++ b/view/ui/DialogAverageImage.ui @@ -0,0 +1,105 @@ + + + AverageImageDialog + + + + 0 + 0 + 844 + 639 + + + + Image Dialog + + + + + + + + + + 0 + 0 + + + + + + + + + + Image Number + + + + + + + + + + Function + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AverageImageDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AverageImageDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogColorMapPicker.ui b/view/ui/DialogColorMapPicker.ui new file mode 100644 index 0000000..ba27ddf --- /dev/null +++ b/view/ui/DialogColorMapPicker.ui @@ -0,0 +1,177 @@ + + + Dialog + + + + 0 + 0 + 463 + 355 + + + + Edge Color Selector + + + + + + + + + Choose Color + + + + + + + + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + + + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + 100.000000000000000 + + + + + + + + + + Values Range + + + + + + + Color Map + + + + + + + Choose Out of Bounds Colors + + + true + + + + + + + + + + + 0 + 0 + + + + + 0 + 70 + + + + + + + + Choose Color + + + + + + + Lower Bounds Color + + + + + + + Upper Bounds Color + + + + + + + Reset Range + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogContrast.ui b/view/ui/DialogContrast.ui new file mode 100644 index 0000000..0e49bf6 --- /dev/null +++ b/view/ui/DialogContrast.ui @@ -0,0 +1,189 @@ + + + ContrastDialog + + + + 0 + 0 + 1031 + 640 + + + + Contrast Dialog + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + Settings + + + + + + + + + + 0 + 0 + + + + Contrast Adjustments + + + + + + Logarithmic Color Range + + + + + + + Full Range + + + true + + + + + + + 4 + + + 100.000000000000000 + + + 99.000000000000000 + + + + + + + Absolute + + + + + + + 4 + + + -10000000000.000000000000000 + + + 1000000000000.000000000000000 + + + 0.100000000000000 + + + + + + + Percentile + + + + + + + 4 + + + -10000000000.000000000000000 + + + 1000000000000.000000000000000 + + + 0.100000000000000 + + + + + + + 4 + + + 99.999899999999997 + + + 1.000000000000000 + + + + + + + + + + + + + + + buttonBox + accepted() + ContrastDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ContrastDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogDensity.ui b/view/ui/DialogDensity.ui new file mode 100644 index 0000000..fea0ceb --- /dev/null +++ b/view/ui/DialogDensity.ui @@ -0,0 +1,269 @@ + + + Dialog + + + + 0 + 0 + 864 + 1244 + + + + Dialog + + + + + + + 500 + 16777215 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Visualization + + + + + + + + + Image Number + + + + + + + 100000 + + + + + + + + + + + + + Color + + + + + + + + + Color Map + + + + + + + + + + + + + + + + NaN Color + + + + + + + Color + + + + + + + + + + + + + Logarithmic color scale + + + + + + + + + + Color Range + + + + + + + + + Default + + + true + + + buttonGroup + + + + + + + + + + + + + Absolute + + + buttonGroup + + + + + + + + + + 999999999 + + + 100 + + + + + + + + + + + + + Percentile + + + buttonGroup + + + + + + + 1 + + + + + + + 99 + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + diff --git a/view/ui/DialogExport.ui b/view/ui/DialogExport.ui new file mode 100644 index 0000000..c5ecc62 --- /dev/null +++ b/view/ui/DialogExport.ui @@ -0,0 +1,641 @@ + + + DialogExport + + + + 0 + 0 + 747 + 874 + + + + Export as Video + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Additional Features + + + + + + Show Scale + + + + + + + + + + + + + + + + + + + + + Show Features + + + + + + + + + + + + + + Show Rag + + + + + + + + + R + + + + + + + 255 + + + + + + + G + + + + + + + 255 + + + + + + + B + + + + + + + 255 + + + + + + + + + Background color + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + General Settings + + + + + + Frames per second + + + + + + + From Frame + + + + + + + + + + + + + true + + + Browse Filesystem + + + + + + + + + + + + 10000000 + + + 500 + + + + + + + x + + + Qt::AlignCenter + + + + + + + 100000 + + + 500 + + + + + + + + + Render Backend + + + + + + + + + + + + 100000 + + + + + + + to + + + Qt::AlignCenter + + + + + + + 1000000 + + + + + + + + + Resolution + + + + + + + 1 + + + + + + + filepath: + + + + + + + File format + + + + + + + + .ogv + + + + + .mp4 + + + + + .avi + + + + + .gif + + + + + + + + + + + Time Stamp Settings + + + + + + , + + + Qt::AlignCenter + + + + + + + Unit + + + + + + + + + + Green + + + + + + + Red + + + + + + + 255 + + + + + + + Blue + + + + + + + 255 + + + + + + + 255 + + + + + + + + + + 4 + + + + + + + Digits + + + + + + + X + + + Qt::AlignCenter + + + + + + + 1 + + + + minutes + + + + + seconds + + + + + milliseconds + + + + + microseconds + + + + + + + + 4 + + + + + + + 4 + + + -100000.000000000000000 + + + 100000.000000000000000 + + + + + + + Color + + + + + + + Show Time Stamps + + + + + + + Start Time + + + + + + + Y + + + Qt::AlignCenter + + + + + + + Show Scan Direction + + + + + + + 10000 + + + + + + + + + + + + + true + + + + + + + Font Size + + + + + + + Text Position + + + + + + + 10000 + + + + + + + + + + true + + + + + + + 18 + + + + + + + Text + + + + + + + + + + Start Frame to 0 + + + + + + + + + + Show Preview + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + spinBoxStartFrame + spinBoxEndFrame + spinBoxResolutionX + spinBoxResolutionY + spinBoxColorR + spinBoxColorG + spinBoxColorB + + + + + buttonBoxExport + accepted() + DialogExport + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBoxExport + rejected() + DialogExport + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogExportXYZ.ui b/view/ui/DialogExportXYZ.ui new file mode 100644 index 0000000..f50bfa1 --- /dev/null +++ b/view/ui/DialogExportXYZ.ui @@ -0,0 +1,150 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Export as XYZ + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + 10 + + + -100000000000000.000000000000000 + + + 10000000000.000000000000000 + + + + + + + 10 + + + -100000000000000.000000000000000 + + + 10000000000.000000000000000 + + + + + + + Y-Direction Stretch + + + + + + + + + + X-Direction Stretch + + + + + + + + + + + + + Browse + + + + + + + + + + File Path + + + + + + + Comment + + + + + + + Set to Angstrom + + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogExportZip.ui b/view/ui/DialogExportZip.ui new file mode 100644 index 0000000..00827e0 --- /dev/null +++ b/view/ui/DialogExportZip.ui @@ -0,0 +1,499 @@ + + + DialogExportZip + + + + 0 + 0 + 1020 + 938 + + + + Export as Zip + + + + + + true + + + + + 0 + 0 + 1000 + 918 + + + + + + + filepath: + + + + + + + + + + true + + + Browse Filesystem + + + + + + + Export Video + + + + + + + + R + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + G + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + B + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + + + Background Color + + + + + + + Frames per Second + + + + + + + + .ogv + + + + + .mp4 + + + + + .avi + + + + + + + + 20 + + + + + + + Resolution + + + + + + + File Format + + + + + + + x + + + Qt::AlignCenter + + + + + + + 100000 + + + 500 + + + + + + + 10000000 + + + 500 + + + + + + + Export Video + + + true + + + + + + + + + + Export Vsy + + + + + + Export Visualyse File + + + true + + + + + + + + + + Export Original File + + + + + + Export Original File + + + true + + + + + + + + + + Export Config + + + + + + Export Config File + + + true + + + + + + + + + + Export Filter Log File + + + + + + Export Filter Logs + + + true + + + + + + + + + + Export Feature List + + + + + + Export Feature List + + + true + + + + + + + + + + Export Images + + + + + + Resolution + + + + + + + File Format + + + + + + + 10000000 + + + 500 + + + + + + + x + + + Qt::AlignCenter + + + + + + + + .png + + + + + .jpg + + + + + + + + 10000000 + + + 500 + + + + + + + Export Images + + + + + + + Background Color + + + + + + + + + + R + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + G + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + B + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + spinBoxColorR + spinBoxColorG + spinBoxColorB + + + + + buttonBoxExport + accepted() + DialogExportZip + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBoxExport + rejected() + DialogExportZip + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFeatureClassVisualization.ui b/view/ui/DialogFeatureClassVisualization.ui new file mode 100644 index 0000000..5a521cc --- /dev/null +++ b/view/ui/DialogFeatureClassVisualization.ui @@ -0,0 +1,133 @@ + + + DialogFeatureClassVisualization + + + + 0 + 0 + 554 + 240 + + + + Feature Class Visualization Dialog + + + + + + + + + + + + + Feature Representation + + + + + + Feature Representation + + + + + + + + + + + Disk + + + + + Circle + + + + + Sphere + + + + + + + + Feature Point Size (in image Pixel) + + + + + + + 4 + + + 10000000000.000000000000000 + + + + + + + Feature Thickness (in Percent) + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DialogFeatureClassVisualization + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogFeatureClassVisualization + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFeatureGrid.ui b/view/ui/DialogFeatureGrid.ui new file mode 100644 index 0000000..4885009 --- /dev/null +++ b/view/ui/DialogFeatureGrid.ui @@ -0,0 +1,273 @@ + + + FeatureGridDialog + + + + 0 + 0 + 844 + 639 + + + + Image Dialog + + + + + + + 400 + 400 + + + + + + + + + 0 + 0 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Auto Detect Grid Points + + + + + + + + + Overlap + + + + + + + Minimum Sigma + + + + + + + Feature Auto Detect Function + + + + + + + + + + + + + + The number of intermediate values of standard deviations to consider + + + + + + + + A value between 0 and 1. If the area of two blobs overlaps by a + fraction greater than `threshold`, the smaller blob is eliminated. + + + + + + + + Num Sigma + + + + + + + Maximum Sigma + + + + + + + Threshold + + + + + + + <html><head/><body><p>The minimum standard + deviation for Gaussian kernel. Keep this low to</p><p> detect + smaller blobs.</p></body></html> + + + + + + + + The absolute lower bound for scale space maxima. Local maxima smaller + than thresh are ignored. Reduce this to detect blobs with less + intensities. + + + + + + + + Detect Maxima + + + + + + + The maximum standard deviation for Gaussian kernel. Keep this high to + detect larger blobs + + + + + + + + Detect + + + + + + + + + + Grid Settings + + + + + + 50000.000000000000000 + + + + + + + Select Color + + + + + + + Function + + + + + + + Point Size + + + + + + + + + + Clear All + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + FeatureGridDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FeatureGridDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFeatureRag.ui b/view/ui/DialogFeatureRag.ui new file mode 100644 index 0000000..4007a0f --- /dev/null +++ b/view/ui/DialogFeatureRag.ui @@ -0,0 +1,286 @@ + + + Dialog + + + + 0 + 0 + 797 + 586 + + + + + 0 + 0 + + + + Dialog + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Feature + + + + + + Feature Class + + + + + + + + + + Feature + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + + 0 + 0 + + + + Settings + + + + + + Image Number + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Force Recreate Rags + + + + + + + + + + Analyse + + + + + + + + + Export + + + + + + + + + + + + + + + + + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 257 + 576 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 325 + 576 + + + 286 + 274 + + + + + spinBox_image_number + valueChanged(int) + horizontalSlider_image_number + setValue(int) + + + 675 + 192 + + + 584 + 373 + + + + + horizontalSlider_image_number + valueChanged(int) + spinBox_image_number + setValue(int) + + + 680 + 375 + + + 669 + 205 + + + + + diff --git a/view/ui/DialogFilter_1D.ui b/view/ui/DialogFilter_1D.ui new file mode 100644 index 0000000..152d094 --- /dev/null +++ b/view/ui/DialogFilter_1D.ui @@ -0,0 +1,248 @@ + + + Dialog_filter + + + + 0 + 0 + 985 + 677 + + + + Filter Dialog + + + + + + + + + + + + Real Image + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + + + + + + + + 0 + 0 + + + + 1D - Signal + + + + + + + + 200 + 200 + + + + + + + + Autoscale + + + + + + + + + + + + + + 0 + 0 + + + + 1D - Fourier Transformed (log) + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + Autosacle + + + + + + + + + + + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + true + + + + + + + Apply Filters + + + + + + + + + + + + + Add + + + + + + + Delete + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog_filter + accept() + + + 257 + 580 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog_filter + reject() + + + 325 + 580 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFilter_1D_Full_Signal.ui b/view/ui/DialogFilter_1D_Full_Signal.ui new file mode 100644 index 0000000..8ec3565 --- /dev/null +++ b/view/ui/DialogFilter_1D_Full_Signal.ui @@ -0,0 +1,236 @@ + + + Dialog_filter + + + + 0 + 0 + 985 + 677 + + + + Filter Full Signal Dialog + + + + + + + + + + + + Real Image + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + + + + + + + 1D - Signal + + + + + + + + 200 + 200 + + + + + + + + Autoscale + + + + + + + + + + + + + 1D - Fourier Transformed (log) + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + Autosacle + + + + + + + + + + + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + true + + + + + + + Apply Filters + + + + + + + + + + + + + Add + + + + + + + Delete + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog_filter + accept() + + + 257 + 580 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog_filter + reject() + + + 325 + 580 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFilter_2D.ui b/view/ui/DialogFilter_2D.ui new file mode 100644 index 0000000..2364af3 --- /dev/null +++ b/view/ui/DialogFilter_2D.ui @@ -0,0 +1,263 @@ + + + Dialog_filter + + + + 0 + 0 + 827 + 592 + + + + Filter Dialog + + + + + + + + + + + + Real Image + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + + + + + + + Fourier Transformed (log) + + + Qt::AlignCenter + + + + + + + + 200 + 200 + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + true + + + + + + + Apply Filters + + + + + + + + + + + + + Add + + + + + + + Delete + + + + + + + + + + + 0 + 0 + + + + + + + Filter Class + + + + + + + + + + + + Saved Filters + + + + + + + + + + + + Save + + + + + + + + + + Filter Inputs + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog_filter + accept() + + + 257 + 580 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog_filter + reject() + + + 325 + 580 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogFullscreen.ui b/view/ui/DialogFullscreen.ui new file mode 100644 index 0000000..7558986 --- /dev/null +++ b/view/ui/DialogFullscreen.ui @@ -0,0 +1,24 @@ + + + DialogFullscreen + + + + 0 + 0 + 409 + 267 + + + + Dialog + + + + + + + + + + diff --git a/view/ui/DialogImport.ui b/view/ui/DialogImport.ui new file mode 100644 index 0000000..fd0b5ee --- /dev/null +++ b/view/ui/DialogImport.ui @@ -0,0 +1,114 @@ + + + DialogImport + + + + 0 + 0 + 573 + 302 + + + + Dialog + + + + + + File Path + + + + + + + Number of Bins + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 100000 + + + 200 + + + + + + + Browse Filesystem + + + + + + + + + buttonBox + accepted() + DialogImport + accept() + + + 227 + 233 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogImport + reject() + + + 268 + 239 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogImportFeatures.ui b/view/ui/DialogImportFeatures.ui new file mode 100644 index 0000000..90bbdff --- /dev/null +++ b/view/ui/DialogImportFeatures.ui @@ -0,0 +1,121 @@ + + + Dialog + + + + 0 + 0 + 287 + 275 + + + + Dialog + + + + + + X Ratio + + + + + + + File Path + + + + + + + + + + Y Ratio + + + + + + + Browse + + + + + + + 4 + + + 10000.000000000000000 + + + 1.000000000000000 + + + + + + + 4 + + + 10000.000000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogImportGWY.ui b/view/ui/DialogImportGWY.ui new file mode 100644 index 0000000..a91fabe --- /dev/null +++ b/view/ui/DialogImportGWY.ui @@ -0,0 +1,123 @@ + + + DialogImportGWY + + + + 0 + 0 + 574 + 300 + + + + Import gwy file + + + + + + + + + + + + false + + + + + + + + + + Browse Filesystem + + + + + + + File Path: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Channel: + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DialogImportGWY + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogImportGWY + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogImportH5.ui b/view/ui/DialogImportH5.ui new file mode 100644 index 0000000..f4e212a --- /dev/null +++ b/view/ui/DialogImportH5.ui @@ -0,0 +1,344 @@ + + + DialogImportH5 + + + + 0 + 0 + 636 + 471 + + + + Import h5 file + + + + + + + + + Out + + + + + + + Import images from + + + + + + + 100000 + + + 100 + + + + + + + File Path: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + <html><head/><body><p>Removes the offset of the z-values (value = value - min(values))</p></body></html> + + + Remove Offset + + + + + + + <html><head/><body><p>If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters</p></body></html> + + + Full scan + + + + + + + Browse Filesystem + + + + + + + + + + 1000000 + + + + + + + to + + + Qt::AlignCenter + + + + + + + 1000000 + + + + + + + + + + To Grid Method + + + + + + + <html><head/><body><p>Maps the z-values to the ideal x-y values from the h5 file. Has higher priority then first-image, if both are checked</p></body></html> + + + + + + + + + + <html><head/><body><p>Maps the z-values of all images after the first image to the x-y values of the first image (used to check if it is necessary to track the x-y values for all images)</p></body></html> + + + First Image X-Y Values + + + + + + + In and Out combined + + + false + + + + + + + <html><head/><body><p>Maps the z-values of all images after the first image to the x-y values of the first image (used to check if it is necessary to track the x-y values for all images)</p></body></html> + + + + + + + + + + <html><head/><body><p>If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters</p></body></html> + + + Visualyse Window + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Ideal X-Y Values + + + + + + + Scan Directions + + + + + + + In and Out seperated + + + + + + + Number of Bins + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + <html><head/><body><p>If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters</p></body></html> + + + Square inside of the circle + + + true + + + + + + + <html><head/><body><p>Removes the frames at the end with a significant smaller range than the images before</p></body></html> + + + + + + true + + + + + + + <html><head/><body><p>Removes the frames at the end with a significant smaller range than the images before</p></body></html> + + + Cut Zoom + + + + + + + + + + In + + + + + + + + + + <html><head/><body><p>Removes the offset of the z-values (value = value - min(values))</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked and the file has a 4th column the 4th column is used as z-values</p></body></html> + + + 4th column as z + + + + + + + <html><head/><body><p>If checked and the file has a 4th column the 4th column is used as z-values</p></body></html> + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DialogImportH5 + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogImportH5 + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogImportNpy.ui b/view/ui/DialogImportNpy.ui new file mode 100644 index 0000000..1af4032 --- /dev/null +++ b/view/ui/DialogImportNpy.ui @@ -0,0 +1,138 @@ + + + DialogImportNpy + + + + 0 + 0 + 636 + 425 + + + + Import h5 file + + + + + + + + + Browse Filesystem + + + + + + + To Grid Method + + + + + + + + + + + + + Number of Bins + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + 100000 + + + 100 + + + + + + + File Path: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DialogImportNpy + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogImportNpy + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogJumpVisualization.ui b/view/ui/DialogJumpVisualization.ui new file mode 100644 index 0000000..5293cdd --- /dev/null +++ b/view/ui/DialogJumpVisualization.ui @@ -0,0 +1,176 @@ + + + Dialog + + + + 0 + 0 + 1015 + 828 + + + + Feature Jump Visualization + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + Feature + + + + + + Feature Class + + + + + + + + + + Feature + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + Check All + + + + + + + Invert + + + + + + + Uncheck All + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Settings + + + + + + The threshold when a movement is detected as a jump + + + Jump Threshold: + + + + + + + The threshold when a movement is detected as a jump + + + + + + + + + + Jump Information + + + + + + + + + + + + + + + + diff --git a/view/ui/DialogLineProfile.ui b/view/ui/DialogLineProfile.ui new file mode 100644 index 0000000..6fddada --- /dev/null +++ b/view/ui/DialogLineProfile.ui @@ -0,0 +1,222 @@ + + + Dialog + + + + 0 + 0 + 1207 + 777 + + + + Dialog + + + + + + + + + + + + + + + + 0 + 0 + + + + Line Profile + + + + + + Remove Line Profile Offset + + + + + + + + + + None + + + + + + + Whole Image + + + + + + + Line Profile Range + + + + + + + + + + Height Profile Scale + + + + + + + + + + X Axis Unit Prefix + + + + + + + + + + Y Axis Unit Prefix + + + + + + + + + + The drawn line can be moved with the arrow keys + + + Line Movement Speed + + + + + + + The line can be rotated with the "insert" and "delete" key + + + Line Rotation Speed + + + + + + + + + + + + + + + + + + + Calculate new step size + + + + + + + Add Line + + + + + + + Remove Line + + + + + + + Export Dialog + + + + + + + Use Arrow Keys to move the selected line, insert and delete for rotation + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogMeasuringPoints.ui b/view/ui/DialogMeasuringPoints.ui new file mode 100644 index 0000000..e8c5e70 --- /dev/null +++ b/view/ui/DialogMeasuringPoints.ui @@ -0,0 +1,443 @@ + + + Dialog + + + + 0 + 0 + 864 + 1194 + + + + Dialog + + + + + + + 500 + 16777215 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Ideal Measuring Points + + + + + + 100000.000000000000000 + + + 1.000000000000000 + + + + + + + Plot + + + + + + + Line Color + + + + + + + Color + + + + + + + + + + Size + + + + + + + Show Ideal Measuring Points + + + + + + + + + + Real Measuring Points + + + + + + Animate + + + + + + + + + + 100000.000000000000000 + + + 1.000000000000000 + + + + + + + Size + + + + + + + Color + + + + + + + Line Color + + + + + + + Plot + + + + + + + + + + + + + Show Measuring Points Z-Value + + + + + + + Show Measuring Points Velocity + + + + + + + None + + + + + + + + + + Points to skip for animation + + + + + + + 1000 + + + 10 + + + + + + + + + + + + + Visualization + + + + + + + + + Image Number + + + + + + + 100000 + + + + + + + + + + + + + Image + + + + + + Show Image + + + + + + + + + + Image Color Map + + + + + + + + + + + + + + + + NaN Color + + + + + + + Color + + + + + + + + + + + + + Selection Information + + + + + + + + + Show Time Diagram + + + + + + + Show Velocity Diagram + + + + + + + Select All + + + + + + + Show Ideal Points Velocity Diagram + + + + + + + Plot Angle Velocity + + + + + + + + + Spiral Middle Point + + + + + + + <html><head/><body><p>Calculates the middle point from the range of the datapoints</p></body></html> + + + Middle Point + + + false + + + + + + + <html><head/><body><p>Choose a point index which is treated as the middle point of the spiral. Index -1 is last point</p></body></html> + + + + + + + <html><head/><body><p>Choose a point index which is treated as the middle point of the spiral. Index -1 is last point</p></body></html> + + + Point Index + + + true + + + + + + + Plot Angular Velocity Diagram + + + + + + + Plot Ideal Angular Velocity Diagram + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogNeuralNetworks.ui b/view/ui/DialogNeuralNetworks.ui new file mode 100644 index 0000000..960ce29 --- /dev/null +++ b/view/ui/DialogNeuralNetworks.ui @@ -0,0 +1,278 @@ + + + Dialog + + + + 0 + 0 + 493 + 765 + + + + Dialog + + + + + + + + + <html><head/><body><p>Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository</p></body></html> + + + + + + + <html><head/><body><p>Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository</p></body></html> + + + Starting Weights Path + + + + + + + <html><head/><body><p>The training data will be generated from the images and the features</p></body></html> + + + + + + + <html><head/><body><p>Number of Training Iterations</p></body></html> + + + Number of Epochs + + + + + + + <html><head/><body><p>Number of Training Iterations</p></body></html> + + + 1 + + + 99999999 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p><a href="https://github.com/ultralytics/yolov5"><span style=" text-decoration: underline; color:#0000ff;">Yolo Path should point to the yolov5 git repository</span></a></p></body></html> + + + + + + + + + + <html><head/><body><p>Results will be saved to runs/{train|detect}/{name}{increasing number} in the yolov5 folder</p></body></html> + + + Name + + + + + + + <html><head/><body><p>Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository</p></body></html> + + + Browse Filesystem + + + + + + + <html><head/><body><p><a href="https://github.com/ultralytics/yolov5"><span style=" text-decoration: underline; color:#0000ff;">Yolo Path should point to the yolov5 git repository</span></a></p></body></html> + + + Yolo Path + + + + + + + <html><head/><body><p>Path where the training/detection data should be saved</p></body></html> + + + Data Path + + + + + + + <html><head/><body><p><a href="https://github.com/ultralytics/yolov5"><span style=" text-decoration: underline; color:#0000ff;">Yolo Path should point to the yolov5 git repository</span></a></p></body></html> + + + Browse Filesystem + + + + + + + <html><head/><body><p>The training data will be generated from the images and the features</p></body></html> + + + Browse Filesystem + + + + + + + Additional parameters + + + + + + + + + + <html><head/><body><p>Results will be saved to runs/{train|detect}/{name}{increasing number} in the yolov5 folder</p></body></html> + + + + + + + <html><head/><body><p><a href="https://arxiv.org/abs/1804.07612"><span style=" text-decoration: underline; color:#0000ff;">&quot;Friends don’t let friends use mini-batches larger than 32“ + + + Batch Size + + + + + + + <html><head/><body><p><a href="https://arxiv.org/abs/1804.07612"><span style=" text-decoration: underline; color:#0000ff;">&quot;Friends don’t let friends use mini-batches larger than 32“ + + + 200 + + + 16 + + + + + + + <html><head/><body><p>Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python</p></body></html> + + + + + + + Show Command + + + + + + + Train + + + + + + + Detect + + + + + + + <html><head/><body><p>Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python</p></body></html> + + + Python Interpreter Path + + + + + + + <html><head/><body><p>Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python</p></body></html> + + + Browse Filesystem + + + + + + + Show Command + + + + + + + <html><head/><body><p>Confidence threshold for detection</p><p><br/></p></body></html> + + + 1.000000000000000 + + + 0.050000000000000 + + + 0.500000000000000 + + + + + + + <html><head/><body><p>Confidence threshold for detection</p><p><br/></p></body></html> + + + Confidence Threshold + + + + + + + + + + + diff --git a/view/ui/DialogPointCloud.ui b/view/ui/DialogPointCloud.ui new file mode 100644 index 0000000..dff5f72 --- /dev/null +++ b/view/ui/DialogPointCloud.ui @@ -0,0 +1,171 @@ + + + Dialog + + + + 0 + 0 + 377 + 602 + + + + Dialog + + + + + + + + + + 0 + 0 + + + + GroupBox + + + + + + Color Map Percentile + + + + + + + + + + + + + 100.000000000000000 + + + 100.000000000000000 + + + + + + + + + + Grey Color Map + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Show Points + + + + + + + Z Scale Factor + + + + + + + 3 + + + 10000.000000000000000 + + + 1.000000000000000 + + + + + + + Image Number + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogProgress.ui b/view/ui/DialogProgress.ui new file mode 100644 index 0000000..600a499 --- /dev/null +++ b/view/ui/DialogProgress.ui @@ -0,0 +1,42 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + + + + + + + Test test + + + + + + + 24 + + + + + + + Cancel + + + + + + + + diff --git a/view/ui/DialogRescaleFeatures.ui b/view/ui/DialogRescaleFeatures.ui new file mode 100644 index 0000000..6dd3881 --- /dev/null +++ b/view/ui/DialogRescaleFeatures.ui @@ -0,0 +1,97 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + + + + y scale factor + + + + + + + x scale factor + + + + + + + + + + Autoscale for old files + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/DialogSaasmi.ui b/view/ui/DialogSaasmi.ui new file mode 100644 index 0000000..86fb695 --- /dev/null +++ b/view/ui/DialogSaasmi.ui @@ -0,0 +1,960 @@ + + + Dialog + + + + 0 + 0 + 1288 + 1186 + + + + Dialog + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + + 500 + 0 + + + + + 500 + 16777215 + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + -868 + 484 + 2003 + + + + + QLayout::SetDefaultConstraint + + + 10 + + + + + + + + 0 + + + + Graph + + + + + + + 10 + + + + + Image Number: + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Picked Element + + + + + + + + + Remove + + + + + + + + + + Add Edge to Node + + + + + + + + + + Add + + + + + + + + + + Information + + + + + + + Add Edges by picking Nodes + + + + + + + Add Nodes by clicking + + + + + + + Select Graph Nodes/Edges + + + + + + + None + + + + + + + Select Network Atoms/Edges + + + + + + + + 0 + 0 + + + + + 0 + 180 + + + + + + + + + + + Export + + + + + + Export as Image + + + + + + + + QLayout::SetDefaultConstraint + + + + + Resolution + + + + + + + 100000 + + + 500 + + + + + + + + 0 + 0 + + + + x + + + + + + + 100000 + + + 500 + + + + + + + + + + + + + File Path + + + + + + + + + + Browse + + + + + + + + + + Export as csv + + + + + + + Export RAG + + + + + + + Export Saasmi Properties + + + + + + + Export as xyz-file + + + + + + + Export chosen Network + + + + + + + + + + Settings + + + + + + Graph Settings + + + + + + Rag Color Map + + + + + + + + + + + + + 100 + + + 100 + + + Qt::Horizontal + + + + + + + 100 + + + 100 + + + + + + + + + + Edge Colors + + + + + + + 5000.899999999999636 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Edge Radius + + + + + + + + + + Node Radius + + + + + + + Node Color + + + + + + + 5000.899999999999636 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + LabelImage Opacity in % + + + + + + + Show Ring Graph + + + + + + + + + + + + + + Recreate Rag + + + + + + + Edge Colors + + + + + + + + + + Color Edges by + + + + + + + Morphology Disk Size + + + + + + + 1000000.989999999990687 + + + + + + + <html><head/><body><p>Only works in matplotlib render backend</p></body></html> + + + Check Edges with Image + + + + + + + <html><head/><body><p>Only works in matplotlib render backend</p></body></html> + + + Mark incorrect edges + + + + + + + + + + Network Settings + + + + + + Show Network + + + + + + + + + + + + + + Show Atoms + + + + + + + + + + + + + + 5000.899999999999636 + + + 0.100000000000000 + + + + + + + Oxygen Atom Radius + + + + + + + 5000.899999999999636 + + + 0.100000000000000 + + + + + + + Color + + + + + + + Silicon Atom Color + + + + + + + Oxygen Atom Color + + + + + + + Color + + + + + + + Recreate Atom Network + + + + + + + Silicon Atom Radius + + + + + + + 5000.899999999999636 + + + 0.100000000000000 + + + + + + + Ring Thickness + + + + + + + Ring Color + + + + + + + Ring Edge Color + + + + + + + Ring Type + + + + + + + + + + + + + + + + + Only working with matplotlib + + + Show Network Polygons + + + + + + + Color Ring by + + + + + + + + + + + + + Update + + + + + + + + + + + + + + Analysis + + + + + + Settings + + + + + + Analyse + + + + + + + + + + Analysis + + + + + + + + + + + + Areas + + + + + + Area Type + + + + + + + + + + positive + + + true + + + + + + + negative + + + false + + + + + + + + + + + 0 + 0 + + + + + + + + Remove Polygon + + + + + + + + + + Image Masks from File + + + + + + Add Image Mask + + + + + + + + 0 + 0 + + + + + + + + Remove Image Mask + + + + + + + + + + + + + + Graph Selection + + + + + + + Visualization Backend + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + 500 + 0 + + + + + + + + + + + + + buttonBox + rejected() + Dialog + reject() + + + 349 + 826 + + + 286 + 274 + + + + + buttonBox + accepted() + Dialog + accept() + + + 281 + 826 + + + 157 + 274 + + + + + spinBox_label_image_opacity + valueChanged(int) + horizontalSlider_label_image_opacity + setValue(int) + + + 1093 + 589 + + + 1170 + 594 + + + + + horizontalSlider_label_image_opacity + valueChanged(int) + spinBox_label_image_opacity + setValue(int) + + + 1260 + 595 + + + 1083 + 600 + + + + + diff --git a/view/ui/DialogSettings.ui b/view/ui/DialogSettings.ui new file mode 100644 index 0000000..b46a9a2 --- /dev/null +++ b/view/ui/DialogSettings.ui @@ -0,0 +1,627 @@ + + + DialogSettings + + + + 0 + 0 + 548 + 561 + + + + Settings + + + + + + 1 + + + + Program + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Hardware + + + + + + Concurrency + + + + + + + + + + + + + + Concurrency + + + + + + + Multiprocessing for 1D Filter + + + + + + + + + + + + + + + + + + + + Multiprocessing for 2D Filter + + + + + + + + + + Visualization + + + + + + + VTK + + + + + Matplotlib + + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + Main Window + + + + + + + Filter 2D Image + + + + + + + Filter 1D Image Window + + + + + + + Filter 1D Signal Window + + + + + + + Matplotlib Toolbar + + + + + + + + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + Filter 1D FFT Window + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + Filter 2D FFT + + + + + + + Second Window + + + + + + + + VTK + + + + + Matplotlib + + + + + + + + + + + + + + + Show Second Render Window + + + + + + + + + + + Time Stamps + + + + + + 1 + + + + minutes + + + + + seconds + + + + + milliseconds + + + + + microseconds + + + + + + + + Font Size + + + + + + + Unit + + + + + + + Digits + + + + + + + Text Position + + + + + + + , + + + Qt::AlignCenter + + + + + + + Show Time Stamps + + + + + + + 4 + + + -100000.000000000000000 + + + 100000.000000000000000 + + + + + + + + + + true + + + + + + + + + + Start Time + + + + + + + 4 + + + + + + + 10000 + + + + + + + Current Frames Time + + + + + + + 10000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Color + + + + + + + + + + Red + + + + + + + Green + + + + + + + 255 + + + + + + + Blue + + + + + + + Color Picker + + + + + + + 255 + + + + + + + 255 + + + + + + + + + + 18 + + + + + + + 4 + + + + + + + X + + + Qt::AlignCenter + + + + + + + Y + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Show Scan Direction + + + + + + + + + + true + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + tabWidget_scan + spinBox_timestamps_pre_decimals + spinBox_timestamps_decimals + spinBox_timestamps_text_font_size + spinBox_timestamps_red + spinBox_timestamps_green + spinBox_timestamps_blue + pushButton_color_picker + doubleSpinBox_start_time + comboBox_timestamps_unit + pushButton_start_time_to_current_frame + checkBox_timestamps_show + spinBox_timestamps_position_y + spinBox_timestamps_position_x + + + + + buttonBox + accepted() + DialogSettings + accept() + + + 257 + 378 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogSettings + reject() + + + 325 + 378 + + + 286 + 274 + + + + + diff --git a/view/ui/PlotableDialog.ui b/view/ui/PlotableDialog.ui new file mode 100644 index 0000000..908da1e --- /dev/null +++ b/view/ui/PlotableDialog.ui @@ -0,0 +1,125 @@ + + + Dialog + + + + 0 + 0 + 903 + 573 + + + + Dialog + + + + + + + 0 + 0 + + + + Settings + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Information + + + + + + + Show Empty Datasets + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/view/ui/__init__.py b/view/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/view/ui/main_window.ui b/view/ui/main_window.ui new file mode 100644 index 0000000..eaeea73 --- /dev/null +++ b/view/ui/main_window.ui @@ -0,0 +1,2966 @@ + + + MainWindow + + + + 0 + 0 + 2297 + 1236 + + + + Visualyse + + + + true + + + + 0 + 0 + + + + + + + Qt::Horizontal + + + + + QLayout::SetDefaultConstraint + + + + + + + + Qt::Vertical + + + + QFrame::NoFrame + + + Qt::Horizontal + + + + false + + + + 0 + 0 + + + + + QLayout::SetMaximumSize + + + + + Qt::Vertical + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + QLayout::SetMinAndMaxSize + + + 15 + + + + + + + + + + + + 0 + 0 + + + + + QLayout::SetFixedSize + + + + + + + + Cursor Position + + + + + + + (0, 0, 0) + + + + + + + + + + + 0 + 0 + + + + + + + false + + + Play + + + + + + + with + + + Qt::AlignCenter + + + + + + + false + + + 10000000.000000000000000 + + + 1.000000000000000 + + + + + + + frames per second + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Scan + + + + + + + + + + + + false + + + + QLayout::SetMaximumSize + + + + + Qt::Vertical + + + + false + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + + + + 0 + 0 + + + + + QLayout::SetMinAndMaxSize + + + 15 + + + 9 + + + + + + + + + + + + 0 + 0 + + + + + + + synchronise + + + + + + + + + + Scan + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 200 + + + + true + + + + + 0 + 0 + 1653 + 198 + + + + + + + Logs: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + + + + Clear Logs + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 600 + 16777215 + + + + + + + true + + + true + + + + + -76 + -72 + 677 + 1256 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 4 + + + + Scan + + + + + + Scan Information + + + + + + + + + + false + + + Scan + + + + + + false + + + + + + Current Scan + + + + + + + + + + Delete + + + + + + + Image Class + + + + + + + + + + Delete + + + + + + + + + + + + + Autosave every + + + + + + + 1 + + + 100000 + + + 30 + + + + + + + Seconds + + + + + + + + + + Show Measuring Points + + + + + + + Plot h5 Infos + + + + + + + Show Point Cloud + + + + + + + Show Density Plot + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + Update + + + + + + + Step Size X + + + + + + + Step Size Y + + + + + + + <html><head/><body><p>Line profiles can be used to check the profile of a line on a given image or to adjust the step size to the real value of the image</p></body></html> + + + Line Profile Dialog + + + + + + + + + + + Filter + + + + + + false + + + Filter 1D + + + + + + Filter Dialog + + + + + + + Use Visualize Function + + + + + + + + + + + + + false + + + Filter 2D + + + + + + Filter Dialog + + + + + + + Use Visualize Function + + + + + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + Filter Full 1D Signal + + + + + + Filter Dialog + + + + + + + Number of Points + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1000000000 + + + 20000 + + + + + + + + + + + + + + Visualization + + + + + + false + + + Visualization + + + + + + Show Average Image + + + + + + + + + + + + + + + + + Show Rag in Image + + + + + + + Display original image + + + + + + + + + + + + + + Show Features + + + + + + + + + + true + + + + + + + + + + Qt::NoFocus + + + 3 + + + + Image + + + + + + Invert Image + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Fouier Transformed + + + + + + Method + + + + + + + + Magnitude Spectrum + + + + + Phase Spectrum + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Image Comparison + + + + + + Compare to Image: + + + + + + + + + + Compare Method + + + + + + + SSIM + + + + SSIM + + + + + Deviation Map + + + + + Cross-correlation + + + + + Background Subtraction + + + + + Background Subtraction (Detect Background for all images) + + + + + + + + Compare Scan + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Contours + + + + + + + Canny + + + + + Laplacian of Gaussian Blob + + + + + Difference of Gaussian Blob + + + + + Determinant of Hessian Blob + + + + + + + + Method + + + + + + + + + + + + + Sigma + + + + + + + Max Sigma + + + + + + + Overlap + + + + + + + Min Sigma + + + + + + + The maximum standard deviation for Gaussian kernel. Keep this high to + detect larger blobs. + + + + 5 + + + + + + + A value between 0 and 1. If the area of two blobs overlaps by a + fraction greater than `threshold`, the smaller blob is eliminated. + + + + + + + + The number of intermediate values of standard deviations to consider + between `min_sigma` and `max_sigma` + + + + 10 + + + + + + + Num Sigma + + + + + + + The absolute lower bound for scale space maxima. Local maxima smaller + than thresh are ignored. Reduce this to detect blobs with less + intensities. + + + + 0.200000000000000 + + + + + + + threshold + + + + + + + The minimum standard deviation for Gaussian kernel. Keep this low to + detect smaller blobs. + + + + 3 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Apply + + + + + + + + + + + + + + + + + 3 + + + 1.000000000000000 + + + 0.100000000000000 + + + 0.000000000000000 + + + + + + + 255 + + + + + + + 255 + + + + + + + Change the Color for areas without a measuring point in RGBA + + + NaN Color + + + + + + + 255 + + + 0 + + + + + + + Background Color + + + + + + + 255 + + + + + + + 255 + + + + + + + 255 + + + + + + + 3 + + + 1.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + + Z Value Scale + + + + + + + 4 + + + 100000.000000000000000 + + + 1.000000000000000 + + + + + + + + + + Contrast Adjustments + + + + + + 4 + + + -10000000000.000000000000000 + + + 1000000000000.000000000000000 + + + 0.100000000000000 + + + + + + + 4 + + + 99.999899999999997 + + + 1.000000000000000 + + + + + + + 4 + + + 100.000000000000000 + + + 99.000000000000000 + + + + + + + 4 + + + -10000000000.000000000000000 + + + 1000000000000.000000000000000 + + + 0.100000000000000 + + + + + + + Percentile + + + true + + + + + + + Absolute + + + + + + + Full Range + + + false + + + + + + + Logarithmic Color Range + + + true + + + + + + + Open Contrast Dialog + + + + + + + + + + + + + + Drift Correction + + + + + + false + + + Drift Correction by Feature + + + + + + Correct Drift by Drift Feature + + + + + + + Generate Drift Feature Class + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Drift Correction by RANSAC + + + + + + + + + Reference Image Number + + + + + + + Correct Drift by RANSAC + + + + + + + + + + Drift Correction by Phase Cross Correlation + + + + + + Correct Drift by Phase Cross Correlation + + + + + + + + + + + + + If the drift vector extends the value it is set to the same as the previous vector + + + Maximum Drift Vector Length (in Pixel) + + + + + + + <html><head/><body><p>Set 0 to disable maximum vector length</p></body></html> + + + [5,10,15,25] + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <html><head/><body><p>Set 0 to disable maximum vector length</p></body></html> + + + Image Offset List + + + + + + + If the drift vector extends the value it is set to the same as the previous vector + + + 4 + + + 0.000000000000000 + + + 1000000.000000000000000 + + + 0.000000000000000 + + + + + + + + + + + + + + Feature Controller + + + + + + false + + + 0 + + + + Feature + + + + + + Load from File + + + + + + + Feature Tracker List + + + + + + + Feature_Class_Name + + + + + + + Auto-fill Feature Tracker List + + + + + + + + + + false + + + + + + + Save in File + + + + + + + + + + + 0 + 300 + + + + + + + + + + + Remove Feature + + + + + + + Feature Index + + + Qt::AlignCenter + + + + + + + Rescale Features + + + + + + + Open Analyse Dialog + + + + + + + Feature Points + + + Qt::AlignCenter + + + + + + + Remove Class + + + + + + + + + + Remove Point + + + + + + + Auto Detect Features + + + + + + + + + + Current Image + + + + + + + Clear Image + + + + + + + All Images + + + + + + + + + + Feature Class + + + Qt::AlignCenter + + + + + + + + + + Open Jump Visualization Dialog + + + + + + + + + + All + + + + + + + None + + + + + + + + + + + + + All + + + + + + + None + + + + + + + + + + Neighbor Information Dialog + + + + + + + Generate Training Data + + + + + + + Export Lines as CSV + + + + + + + + + + Copy + + + + + + + + + + Copy List from Image + + + + + + + + + + + + + + + + Copy + + + + + + + Copy Feature List to other images + + + + + + + Force Copy + + + + + + + + + + Add Feature Class + + + + + + + Color + + + + + + + Auto-fill selected Features + + + + + + + + + + + + + + Feature Picking + + + + + + + + + Pick Method + + + + + + + Point + + + + + + + Feature + + + + + + + Pick + + + + + + + Add chosen Point to all Images + + + + + + + + + + + + + + 0 + + + 10000 + + + 0 + + + + + + + 0 for unlimited + + + Maximum Number of Points per Feature + + + + + + + + + + + + + Feature Grid + + + + + + Use Grid + + + + + + + Show Grid + + + + + + + + + + true + + + + + + + + + + false + + + + + + + + + + <html><head/><body><p>Select the folder where the yolo labels are saved. The order of the file names should correspond with the image number (first file = first image)</p></body></html> + + + Load Yolo Label Folder + + + + + + + <html><head/><body><p>Select the folder where the yolo labels are saved. The order of the file names should correspond with the image number (first file = first image)</p></body></html> + + + Load + + + + + + + + Settings + + + + + + Feature Highlighting + + + + + + Feature Highlight Method + + + + + + + + + + Feature Fixed Color + + + + + + + + + + 255 + + + + + + + 255 + + + + + + + 255 + + + + + + + R + + + Qt::AlignCenter + + + + + + + G + + + Qt::AlignCenter + + + + + + + B + + + Qt::AlignCenter + + + + + + + + + + Feature Brightness + + + + + + + 0.600000000000000 + + + 0.100000000000000 + + + + + + + + + + Feature Detection + + + + + + + + + Feature Auto Detect Function + + + + + + + Minimum Sigma + + + + + + + Overlap + + + + + + + <html><head/><body><p>The minimum standard + deviation for Gaussian kernel. Keep this low to</p><p> detect + smaller blobs.</p></body></html> + + + + + + + + A value between 0 and 1. If the area of two blobs overlaps by a + fraction greater than `threshold`, the smaller blob is eliminated. + + + + + + + + Threshold + + + + + + + The number of intermediate values of standard deviations to consider + + + + + + + + The maximum standard deviation for Gaussian kernel. Keep this high to + detect larger blobs + + + + + + + + + + + Num Sigma + + + + + + + Maximum Sigma + + + + + + + The absolute lower bound for scale space maxima. Local maxima smaller + than thresh are ignored. Reduce this to detect blobs with less + intensities. + + + + + + + + Detect Maxima + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Apply current Settings + + + + + + + Feature Representation + + + + + + Feature Representation + + + + + + + + Disk + + + + + Circle + + + + + Sphere + + + + + + + + Feature Point Size (in image Pixel) + + + + + + + 4 + + + 10000000000.000000000000000 + + + + + + + Feature Thickness (in Percent) + + + + + + + + + + <html><head/><body><p>When autodetecting features it + looks in the previous image, if a feature is found within the givin distance the + point will be added to the feature. The distance given here is multiplied by the + step size of the bins</p></body></html> + + + + Auto Detect Feature Distance + + + + + + + 10000000000.000000000000000 + + + + + + + + + + Feature Pick Settings + + + + + + If selected new features will snap to feature grid + + + Use Feature Grid + + + + + + + If selected new features will snap to feature grid + + + + + + + + + + Generate Feature Grid + + + + + + + Apply Feature Grid on Feature Points + + + + + + + + + + + + + + + SAASMI + + + + + + 100000.000000000000000 + + + 250.000000000000000 + + + + + + + Beta: + + + + + + + SAASMI + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Number of k-neighbors + + + + + + + + + + Auto generate RAG + + + + + + + + + + true + + + + + + + Show Prepared Images + + + + + + + Morphology Disk Size + + + + + + + 1000000.000000000000000 + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 2297 + 22 + + + + + File + + + + Import File + + + + + + + + + + + + + + + false + + + Export + + + + + + + + + + + + + + + false + + + Tools + + + + + + false + + + Filter + + + + + + false + + + Layout + + + + + + + + + + + false + + + Save Scan + + + + + Import h5 + + + + + Import vsy + + + + + true + + + Export as video + + + + + true + + + Export as image + + + + + Export as yspar.dat && mask.dat + + + + + true + + + Settings + + + + + Export as Scan Object (.vsy) + + + + + Import filter log + + + + + true + + + Import filter from log + + + + + Import from image file + + + + + false + + + Generate Log File + + + + + Import sxm + + + + + Import gwy + + + + + Export as .gwy + + + + + true + + + Export as zip file + + + + + Import scan zip + + + + + Import avi + + + + + true + + + Reset Horizontal Layout + + + + + Import stp + + + + + Import npy + + + + + Import arn + + + + + lineEdit_step_size_x + lineEdit_step_size_y + spinBox_window_1 + listWidget_filter_1d + pushButton_open_filter_dialog_1d + listWidget_filter_2d + pushButton_open_filter_dialog_2d + checkBox_image_invert + comboBox_fourier_method + spinBox_error_map_compare_image_number + comboBox_compare_method + comboBox_compare_scans + comboBox_contours + spinBox_canny_sigma + spinBox_blob_max_sigma + doubleSpinBox_blob_overlap + spinBox_blob_num_sigma + doubleSpinBox_blob_threshold + spinBox_blob_min_sigma + doubleSpinBox_NaN_A + spinBox_NaN_B + spinBox_NaN_G + spinBox_NaN_R + spinBox_background_g + spinBox_background_r + spinBox_background_b + doubleSpinBox_background_alpha + checkBox_show_original_image + doubleSpinBox_z_scale + doubleSpinBox_contrast_absolute_min + doubleSpinBox_contrast_percentile_min + doubleSpinBox_contrast_percentile_max + doubleSpinBox_contrast_absolute_max + radioButton_contrast_percentile + radioButton_contrast_absolute + radioButton_full_range + checkBox_fourier_logarithmic + comboBox_set_current_scan + radioButton_pick_method_point + radioButton_pick_method_feature + radioButton_pick_method_pick + pushButton_load_feature_tracker_list + comboBox_scan_window_1 + checkBox_feature_tracker_auto_fill + pushButton_save_feature_tracker_list + listWidget_features + listWidget_feature_points + listWidget_feature_classes + pushButton_remove_feature_class + pushButton_remove_feature + pushButton_remove_feature_point + pushButton_feature_tracker_image_copy + spinBox_feature_tracker_image_copy + pushButton_copy_feature + pushButton_force_copy_feature + horizontalSlider_window_1 + pushButton_set_feature_class_color + spinBox_window_2 + comboBox_scan_window_2 + checkBox_snychronise + horizontalSlider_window_2 + scrollArea + textBrowser_logs + pushButton_clear_logs + scrollArea_2 + tabWidget_controller + pushButton_scan_delete + comboBox_scan_image_class + pushButton_delete_current_image_class + checkBox_enable_autosave + spinBox_autosave_in_seconds + pushButton_show_measuring_points + textBrowser_scan_information + checkBox_filter_1d_use_visualyze_function + checkBox_filter_2d_use_visualyze_function + pushButton_filter_full_1d_signal + spinBox_full_signal_maximum_number_of_points + listWidget_filter_full_1d_signal + checkBox_show_rag_in_image + checkBox_show_features + pushButton_visualization_contours_apply + pushButton_drift_correction_by_feature + pushButton_generate_drift_feature + spinBox_ransac_reference_image_number + pushButton_ransac_correct_drift + tabWidget_feature_tracker + checkBox_add_to_all_images + spinBox_maximum_number_of_points_per_feature + pushButton_feature_analyse + pushButton_auto_detect_features_current_image + pushButton_auto_detect_feature_clear_image + pushButton_auto_detect_features_all_images + checkBox_auto_fill_selected_features + comboBox_feature_feature_highlight_method + spinBox_feature_highlight_color_g + spinBox_feature_highlight_color_r + spinBox_feature_highlight_color_b + doubleSpinBox_feature_highlight_brightness + comboBox_feature_auto_detect_function + doubleSpinBox_feature_auto_detect_min_sigma + doubleSpinBox_feature_auto_detect_overlap + spinBox_feature_auto_detect_num_sigma + doubleSpinBox_feature_auto_detect_max_sigma + doubleSpinBox_feature_auto_detect_threshold + comboBox_feature_representation + doubleSpinBox_feature_point_size + doubleSpinBox_feature_thickness + doubleSpinBox_feature_distance + pushButton_save_feature_settings_as_default + doubleSpinBox_saasmi_beta + pushButton_saasmi + spinBox_number_of_neighbors + checkBox_auto_generate_rag + lineEdit_feature_class_to_add + pushButton_add_feature_class + pushButton_step_size_update + + + + + spinBox_window_2 + valueChanged(int) + horizontalSlider_window_2 + setValue(int) + + + 604 + 144 + + + 726 + 138 + + + + + horizontalSlider_window_2 + valueChanged(int) + spinBox_window_2 + setValue(int) + + + 726 + 138 + + + 604 + 144 + + + + + horizontalSlider_window_1 + valueChanged(int) + spinBox_window_1 + setValue(int) + + + 249 + 138 + + + 84 + 144 + + + + + spinBox_window_1 + valueChanged(int) + horizontalSlider_window_1 + setValue(int) + + + 84 + 144 + + + 199 + 138 + + + + + doubleSpinBox_contrast_percentile_max + valueChanged(QString) + radioButton_contrast_percentile + click() + + + 1382 + 771 + + + 998 + 769 + + + + + doubleSpinBox_contrast_percentile_min + valueChanged(QString) + radioButton_contrast_percentile + click() + + + 1190 + 771 + + + 998 + 769 + + + + + doubleSpinBox_contrast_absolute_min + valueChanged(double) + radioButton_contrast_absolute + click() + + + 1190 + 803 + + + 998 + 801 + + + + + doubleSpinBox_contrast_absolute_max + valueChanged(double) + radioButton_contrast_absolute + click() + + + 1382 + 803 + + + 998 + 801 + + + + + diff --git a/view/ui/ui_dialog_analyse.py b/view/ui/ui_dialog_analyse.py new file mode 100644 index 0000000..b0e611c --- /dev/null +++ b/view/ui/ui_dialog_analyse.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogAnalyse.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(1015, 828) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_analyse_window = QtWidgets.QWidget(self.widget) + self.widget_analyse_window.setObjectName("widget_analyse_window") + self.gridLayout_2.addWidget(self.widget_analyse_window, 1, 0, 1, 2) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 0, 1, 1) + self.textBrowser_feature_analyse = QtWidgets.QTextBrowser(self.widget) + self.textBrowser_feature_analyse.setObjectName("textBrowser_feature_analyse") + self.gridLayout_2.addWidget(self.textBrowser_feature_analyse, 3, 0, 1, 2) + self.label_3 = QtWidgets.QLabel(self.widget) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1) + self.comboBox_analyse_window = QtWidgets.QComboBox(self.widget) + self.comboBox_analyse_window.setObjectName("comboBox_analyse_window") + self.gridLayout_2.addWidget(self.comboBox_analyse_window, 2, 1, 1, 1) + self.gridLayout.addWidget(self.widget, 0, 2, 1, 1) + self.widget_2 = QtWidgets.QWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.groupBox = QtWidgets.QGroupBox(self.widget_2) + self.groupBox.setObjectName("groupBox") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_4.addWidget(self.label, 0, 0, 1, 1) + self.listWidget_feature_class = QtWidgets.QListWidget(self.groupBox) + self.listWidget_feature_class.setObjectName("listWidget_feature_class") + self.gridLayout_4.addWidget(self.listWidget_feature_class, 1, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout_4.addWidget(self.label_2, 3, 0, 1, 1) + self.listWidget_feature = QtWidgets.QListWidget(self.groupBox) + self.listWidget_feature.setObjectName("listWidget_feature") + self.gridLayout_4.addWidget(self.listWidget_feature, 4, 0, 1, 1) + self.widget_3 = QtWidgets.QWidget(self.groupBox) + self.widget_3.setObjectName("widget_3") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pushButton_feature_list_check_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_check_all.setObjectName("pushButton_feature_list_check_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_check_all) + self.pushButton_feature_list_invert = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_invert.setObjectName("pushButton_feature_list_invert") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_invert) + self.pushButton_feature_list_uncheck_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_uncheck_all.setObjectName("pushButton_feature_list_uncheck_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_uncheck_all) + self.gridLayout_4.addWidget(self.widget_3, 5, 0, 1, 1) + self.widget_4 = QtWidgets.QWidget(self.groupBox) + self.widget_4.setObjectName("widget_4") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_4) + self.horizontalLayout.setObjectName("horizontalLayout") + self.pushButton_feature_class_check_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_check_all.setObjectName("pushButton_feature_class_check_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_check_all) + self.pushButton_feature_class_invert = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_invert.setObjectName("pushButton_feature_class_invert") + self.horizontalLayout.addWidget(self.pushButton_feature_class_invert) + self.pushButton_feature_class_uncheck_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_uncheck_all.setObjectName("pushButton_feature_class_uncheck_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_uncheck_all) + self.gridLayout_4.addWidget(self.widget_4, 2, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox, 1, 0, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_3.addItem(spacerItem1, 3, 0, 1, 1) + self.groupBox_2 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_2.setObjectName("groupBox_2") + self.formLayout = QtWidgets.QFormLayout(self.groupBox_2) + self.formLayout.setObjectName("formLayout") + self.label_4 = QtWidgets.QLabel(self.groupBox_2) + self.label_4.setObjectName("label_4") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.lineEdit_jump_threshold = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_jump_threshold.setObjectName("lineEdit_jump_threshold") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.lineEdit_jump_threshold) + self.pushButton_eqaul_number_of_jumps_threshold = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_eqaul_number_of_jumps_threshold.setObjectName("pushButton_eqaul_number_of_jumps_threshold") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.pushButton_eqaul_number_of_jumps_threshold) + self.listWidget_equal_jump_thresholds = QtWidgets.QListWidget(self.groupBox_2) + self.listWidget_equal_jump_thresholds.setObjectName("listWidget_equal_jump_thresholds") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.listWidget_equal_jump_thresholds) + self.checkBox_count_nan_jumps = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_count_nan_jumps.setObjectName("checkBox_count_nan_jumps") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.checkBox_count_nan_jumps) + self.gridLayout_3.addWidget(self.groupBox_2, 2, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_5.setObjectName("gridLayout_5") + self.widget_5 = QtWidgets.QWidget(self.groupBox_3) + self.widget_5.setObjectName("widget_5") + self.gridLayout_6 = QtWidgets.QGridLayout(self.widget_5) + self.gridLayout_6.setObjectName("gridLayout_6") + self.label_5 = QtWidgets.QLabel(self.widget_5) + self.label_5.setObjectName("label_5") + self.gridLayout_6.addWidget(self.label_5, 0, 0, 1, 1) + self.spinBox_start_image_number = QtWidgets.QSpinBox(self.widget_5) + self.spinBox_start_image_number.setObjectName("spinBox_start_image_number") + self.gridLayout_6.addWidget(self.spinBox_start_image_number, 0, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.widget_5) + self.label_6.setAlignment(QtCore.Qt.AlignCenter) + self.label_6.setObjectName("label_6") + self.gridLayout_6.addWidget(self.label_6, 0, 2, 1, 1) + self.spinBox_end_image_number = QtWidgets.QSpinBox(self.widget_5) + self.spinBox_end_image_number.setObjectName("spinBox_end_image_number") + self.gridLayout_6.addWidget(self.spinBox_end_image_number, 0, 3, 1, 1) + self.gridLayout_5.addWidget(self.widget_5, 0, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_3, 0, 0, 1, 1) + self.gridLayout.addWidget(self.widget_2, 0, 3, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_3.setText(_translate("Dialog", "Analyse Window")) + self.groupBox.setTitle(_translate("Dialog", "Feature")) + self.label.setText(_translate("Dialog", "Feature Class")) + self.label_2.setText(_translate("Dialog", "Feature")) + self.pushButton_feature_list_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_list_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_list_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.pushButton_feature_class_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_class_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_class_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.groupBox_2.setTitle(_translate("Dialog", "Settings")) + self.label_4.setToolTip(_translate("Dialog", "The threshold when a movement is detected as a jump")) + self.label_4.setText(_translate("Dialog", "Jump Threshold:")) + self.lineEdit_jump_threshold.setToolTip(_translate("Dialog", "The threshold when a movement is detected as a jump")) + self.pushButton_eqaul_number_of_jumps_threshold.setText(_translate("Dialog", "Get equal number of Jumps for all feature classes")) + self.checkBox_count_nan_jumps.setToolTip(_translate("Dialog", "

Sometimes feautres jump out of the scan or new feature appear in the scan. If this option is checked they will be counted as jump.

")) + self.checkBox_count_nan_jumps.setText(_translate("Dialog", "Count Jumps not inside of the scan")) + self.groupBox_3.setTitle(_translate("Dialog", "Image")) + self.label_5.setText(_translate("Dialog", "From Image")) + self.label_6.setText(_translate("Dialog", "to")) diff --git a/view/ui/ui_dialog_average_image.py b/view/ui/ui_dialog_average_image.py new file mode 100644 index 0000000..42a3859 --- /dev/null +++ b/view/ui/ui_dialog_average_image.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogAverageImage.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_AverageImageDialog(object): + def setupUi(self, AverageImageDialog): + AverageImageDialog.setObjectName("AverageImageDialog") + AverageImageDialog.resize(844, 639) + self.gridLayout = QtWidgets.QGridLayout(AverageImageDialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_render_window = QtWidgets.QWidget(AverageImageDialog) + self.widget_render_window.setObjectName("widget_render_window") + self.gridLayout.addWidget(self.widget_render_window, 0, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(AverageImageDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_2.setObjectName("gridLayout_2") + self.spinBox_image_number = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_2.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget_2) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.comboBox_image_comparison_function = QtWidgets.QComboBox(self.widget_2) + self.comboBox_image_comparison_function.setObjectName("comboBox_image_comparison_function") + self.gridLayout_2.addWidget(self.comboBox_image_comparison_function, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget_2) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.gridLayout.addWidget(self.widget_2, 0, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(AverageImageDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 1, 1, 1) + + self.retranslateUi(AverageImageDialog) + self.buttonBox.accepted.connect(AverageImageDialog.accept) + self.buttonBox.rejected.connect(AverageImageDialog.reject) + QtCore.QMetaObject.connectSlotsByName(AverageImageDialog) + + def retranslateUi(self, AverageImageDialog): + _translate = QtCore.QCoreApplication.translate + AverageImageDialog.setWindowTitle(_translate("AverageImageDialog", "Image Dialog")) + self.label.setText(_translate("AverageImageDialog", "Image Number")) + self.label_2.setText(_translate("AverageImageDialog", "Function")) diff --git a/view/ui/ui_dialog_color_map_picker.py b/view/ui/ui_dialog_color_map_picker.py new file mode 100644 index 0000000..e796d8e --- /dev/null +++ b/view/ui/ui_dialog_color_map_picker.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogColorMapPicker.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(463, 355) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.pushButton_color_upper_bounds = QtWidgets.QPushButton(self.widget) + self.pushButton_color_upper_bounds.setObjectName("pushButton_color_upper_bounds") + self.gridLayout_2.addWidget(self.pushButton_color_upper_bounds, 3, 1, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.doubleSpinBox_color_value_min = QtWidgets.QDoubleSpinBox(self.widget_2) + self.doubleSpinBox_color_value_min.setMinimum(-1000000.0) + self.doubleSpinBox_color_value_min.setMaximum(1000000.0) + self.doubleSpinBox_color_value_min.setObjectName("doubleSpinBox_color_value_min") + self.horizontalLayout.addWidget(self.doubleSpinBox_color_value_min) + self.doubleSpinBox_color_value_max = QtWidgets.QDoubleSpinBox(self.widget_2) + self.doubleSpinBox_color_value_max.setMinimum(-1000000.0) + self.doubleSpinBox_color_value_max.setMaximum(1000000.0) + self.doubleSpinBox_color_value_max.setProperty("value", 100.0) + self.doubleSpinBox_color_value_max.setObjectName("doubleSpinBox_color_value_max") + self.horizontalLayout.addWidget(self.doubleSpinBox_color_value_max) + self.gridLayout_2.addWidget(self.widget_2, 4, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 4, 0, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.checkBox_use_bound_color = QtWidgets.QCheckBox(self.widget) + self.checkBox_use_bound_color.setChecked(True) + self.checkBox_use_bound_color.setObjectName("checkBox_use_bound_color") + self.gridLayout_2.addWidget(self.checkBox_use_bound_color, 1, 0, 1, 2) + self.comboBox_color_map = QtWidgets.QComboBox(self.widget) + self.comboBox_color_map.setObjectName("comboBox_color_map") + self.gridLayout_2.addWidget(self.comboBox_color_map, 0, 1, 1, 1) + self.widget_color_bar = QtWidgets.QWidget(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_color_bar.sizePolicy().hasHeightForWidth()) + self.widget_color_bar.setSizePolicy(sizePolicy) + self.widget_color_bar.setMinimumSize(QtCore.QSize(0, 70)) + self.widget_color_bar.setObjectName("widget_color_bar") + self.gridLayout_2.addWidget(self.widget_color_bar, 6, 0, 1, 2) + self.pushButton_color_lower_bounds = QtWidgets.QPushButton(self.widget) + self.pushButton_color_lower_bounds.setObjectName("pushButton_color_lower_bounds") + self.gridLayout_2.addWidget(self.pushButton_color_lower_bounds, 2, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.widget) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.widget) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 3, 0, 1, 1) + self.pushButton_reset_color_range = QtWidgets.QPushButton(self.widget) + self.pushButton_reset_color_range.setObjectName("pushButton_reset_color_range") + self.gridLayout_2.addWidget(self.pushButton_reset_color_range, 5, 0, 1, 2) + self.gridLayout.addWidget(self.widget, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Edge Color Selector")) + self.pushButton_color_upper_bounds.setText(_translate("Dialog", "Choose Color")) + self.label_2.setText(_translate("Dialog", "Values Range")) + self.label.setText(_translate("Dialog", "Color Map")) + self.checkBox_use_bound_color.setText(_translate("Dialog", "Choose Out of Bounds Colors")) + self.pushButton_color_lower_bounds.setText(_translate("Dialog", "Choose Color")) + self.label_3.setText(_translate("Dialog", "Lower Bounds Color")) + self.label_4.setText(_translate("Dialog", "Upper Bounds Color")) + self.pushButton_reset_color_range.setText(_translate("Dialog", "Reset Range")) diff --git a/view/ui/ui_dialog_contrast.py b/view/ui/ui_dialog_contrast.py new file mode 100644 index 0000000..05ca259 --- /dev/null +++ b/view/ui/ui_dialog_contrast.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogContrast.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ContrastDialog(object): + def setupUi(self, ContrastDialog): + ContrastDialog.setObjectName("ContrastDialog") + ContrastDialog.resize(1031, 640) + self.gridLayout = QtWidgets.QGridLayout(ContrastDialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_image_render = QtWidgets.QWidget(ContrastDialog) + self.widget_image_render.setObjectName("widget_image_render") + self.gridLayout.addWidget(self.widget_image_render, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(ContrastDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2) + self.groupBox = QtWidgets.QGroupBox(ContrastDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_image_histogram = QtWidgets.QWidget(self.groupBox) + self.widget_image_histogram.setObjectName("widget_image_histogram") + self.gridLayout_2.addWidget(self.widget_image_histogram, 0, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_3.setObjectName("gridLayout_3") + self.checkBox_fourier_logarithmic = QtWidgets.QCheckBox(self.groupBox_3) + self.checkBox_fourier_logarithmic.setObjectName("checkBox_fourier_logarithmic") + self.gridLayout_3.addWidget(self.checkBox_fourier_logarithmic, 0, 0, 1, 2) + self.radioButton_full_range = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_full_range.setChecked(True) + self.radioButton_full_range.setObjectName("radioButton_full_range") + self.gridLayout_3.addWidget(self.radioButton_full_range, 2, 0, 1, 2) + self.doubleSpinBox_contrast_percentile_max = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_percentile_max.setDecimals(4) + self.doubleSpinBox_contrast_percentile_max.setMaximum(100.0) + self.doubleSpinBox_contrast_percentile_max.setProperty("value", 99.0) + self.doubleSpinBox_contrast_percentile_max.setObjectName("doubleSpinBox_contrast_percentile_max") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_percentile_max, 3, 2, 1, 1) + self.radioButton_contrast_absolute = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_contrast_absolute.setObjectName("radioButton_contrast_absolute") + self.gridLayout_3.addWidget(self.radioButton_contrast_absolute, 4, 0, 1, 1) + self.doubleSpinBox_contrast_absolute_max = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_absolute_max.setDecimals(4) + self.doubleSpinBox_contrast_absolute_max.setMinimum(-10000000000.0) + self.doubleSpinBox_contrast_absolute_max.setMaximum(1000000000000.0) + self.doubleSpinBox_contrast_absolute_max.setSingleStep(0.1) + self.doubleSpinBox_contrast_absolute_max.setObjectName("doubleSpinBox_contrast_absolute_max") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_absolute_max, 4, 2, 1, 1) + self.radioButton_contrast_percentile = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_contrast_percentile.setObjectName("radioButton_contrast_percentile") + self.gridLayout_3.addWidget(self.radioButton_contrast_percentile, 3, 0, 1, 1) + self.doubleSpinBox_contrast_absolute_min = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_absolute_min.setDecimals(4) + self.doubleSpinBox_contrast_absolute_min.setMinimum(-10000000000.0) + self.doubleSpinBox_contrast_absolute_min.setMaximum(1000000000000.0) + self.doubleSpinBox_contrast_absolute_min.setSingleStep(0.1) + self.doubleSpinBox_contrast_absolute_min.setObjectName("doubleSpinBox_contrast_absolute_min") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_absolute_min, 4, 1, 1, 1) + self.doubleSpinBox_contrast_percentile_min = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_percentile_min.setDecimals(4) + self.doubleSpinBox_contrast_percentile_min.setMaximum(99.9999) + self.doubleSpinBox_contrast_percentile_min.setProperty("value", 1.0) + self.doubleSpinBox_contrast_percentile_min.setObjectName("doubleSpinBox_contrast_percentile_min") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_percentile_min, 3, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_3, 1, 0, 1, 1) + self.gridLayout.addWidget(self.groupBox, 0, 1, 2, 1) + + self.retranslateUi(ContrastDialog) + self.buttonBox.accepted.connect(ContrastDialog.accept) + self.buttonBox.rejected.connect(ContrastDialog.reject) + QtCore.QMetaObject.connectSlotsByName(ContrastDialog) + + def retranslateUi(self, ContrastDialog): + _translate = QtCore.QCoreApplication.translate + ContrastDialog.setWindowTitle(_translate("ContrastDialog", "Contrast Dialog")) + self.groupBox.setTitle(_translate("ContrastDialog", "Settings")) + self.groupBox_3.setTitle(_translate("ContrastDialog", "Contrast Adjustments")) + self.checkBox_fourier_logarithmic.setText(_translate("ContrastDialog", "Logarithmic Color Range")) + self.radioButton_full_range.setText(_translate("ContrastDialog", "Full Range")) + self.radioButton_contrast_absolute.setText(_translate("ContrastDialog", "Absolute")) + self.radioButton_contrast_percentile.setText(_translate("ContrastDialog", "Percentile")) diff --git a/view/ui/ui_dialog_density.py b/view/ui/ui_dialog_density.py new file mode 100644 index 0000000..1bafdf8 --- /dev/null +++ b/view/ui/ui_dialog_density.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogDensity.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(864, 1244) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_2 = QtWidgets.QWidget(Dialog) + self.widget_2.setMaximumSize(QtCore.QSize(500, 16777215)) + self.widget_2.setObjectName("widget_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_2.setObjectName("gridLayout_2") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 3, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_6.setObjectName("gridLayout_6") + self.widget_3 = QtWidgets.QWidget(self.groupBox_3) + self.widget_3.setObjectName("widget_3") + self.gridLayout_7 = QtWidgets.QGridLayout(self.widget_3) + self.gridLayout_7.setObjectName("gridLayout_7") + self.label_5 = QtWidgets.QLabel(self.widget_3) + self.label_5.setObjectName("label_5") + self.gridLayout_7.addWidget(self.label_5, 0, 0, 1, 1) + self.spinBox_image_number = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_image_number.setMaximum(100000) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_7.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.gridLayout_6.addWidget(self.widget_3, 0, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_3, 0, 0, 1, 1) + self.groupBox_4 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_8 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_8.setObjectName("gridLayout_8") + self.widget_4 = QtWidgets.QWidget(self.groupBox_4) + self.widget_4.setObjectName("widget_4") + self.gridLayout_9 = QtWidgets.QGridLayout(self.widget_4) + self.gridLayout_9.setObjectName("gridLayout_9") + self.label_8 = QtWidgets.QLabel(self.widget_4) + self.label_8.setObjectName("label_8") + self.gridLayout_9.addWidget(self.label_8, 0, 0, 1, 1) + self.comboBox_color_map = QtWidgets.QComboBox(self.widget_4) + self.comboBox_color_map.setObjectName("comboBox_color_map") + self.gridLayout_9.addWidget(self.comboBox_color_map, 0, 1, 1, 1) + self.gridLayout_8.addWidget(self.widget_4, 0, 0, 1, 1) + self.widget_5 = QtWidgets.QWidget(self.groupBox_4) + self.widget_5.setObjectName("widget_5") + self.gridLayout_10 = QtWidgets.QGridLayout(self.widget_5) + self.gridLayout_10.setObjectName("gridLayout_10") + self.label_9 = QtWidgets.QLabel(self.widget_5) + self.label_9.setObjectName("label_9") + self.gridLayout_10.addWidget(self.label_9, 0, 0, 1, 1) + self.pushButton_nan_color = QtWidgets.QPushButton(self.widget_5) + self.pushButton_nan_color.setObjectName("pushButton_nan_color") + self.gridLayout_10.addWidget(self.pushButton_nan_color, 0, 1, 1, 1) + self.gridLayout_8.addWidget(self.widget_5, 1, 0, 1, 1) + self.widget = QtWidgets.QWidget(self.groupBox_4) + self.widget.setObjectName("widget") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_3.setObjectName("gridLayout_3") + self.checkBox_logarithmic_color = QtWidgets.QCheckBox(self.widget) + self.checkBox_logarithmic_color.setObjectName("checkBox_logarithmic_color") + self.gridLayout_3.addWidget(self.checkBox_logarithmic_color, 0, 0, 1, 1) + self.gridLayout_8.addWidget(self.widget, 2, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.groupBox_4) + self.groupBox.setObjectName("groupBox") + self.gridLayout_12 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_12.setObjectName("gridLayout_12") + self.widget_8 = QtWidgets.QWidget(self.groupBox) + self.widget_8.setObjectName("widget_8") + self.gridLayout_11 = QtWidgets.QGridLayout(self.widget_8) + self.gridLayout_11.setObjectName("gridLayout_11") + self.radioButton_default = QtWidgets.QRadioButton(self.widget_8) + self.radioButton_default.setChecked(True) + self.radioButton_default.setObjectName("radioButton_default") + self.buttonGroup = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.radioButton_default) + self.gridLayout_11.addWidget(self.radioButton_default, 0, 0, 1, 1) + self.gridLayout_12.addWidget(self.widget_8, 0, 0, 1, 1) + self.widget_6 = QtWidgets.QWidget(self.groupBox) + self.widget_6.setObjectName("widget_6") + self.gridLayout_4 = QtWidgets.QGridLayout(self.widget_6) + self.gridLayout_4.setObjectName("gridLayout_4") + self.radioButton_absolute = QtWidgets.QRadioButton(self.widget_6) + self.radioButton_absolute.setObjectName("radioButton_absolute") + self.buttonGroup.addButton(self.radioButton_absolute) + self.gridLayout_4.addWidget(self.radioButton_absolute, 0, 0, 1, 1) + self.spinBox_absolute_min = QtWidgets.QSpinBox(self.widget_6) + self.spinBox_absolute_min.setObjectName("spinBox_absolute_min") + self.gridLayout_4.addWidget(self.spinBox_absolute_min, 0, 1, 1, 1) + self.spinBox_absolute_max = QtWidgets.QSpinBox(self.widget_6) + self.spinBox_absolute_max.setMaximum(999999999) + self.spinBox_absolute_max.setProperty("value", 100) + self.spinBox_absolute_max.setObjectName("spinBox_absolute_max") + self.gridLayout_4.addWidget(self.spinBox_absolute_max, 0, 2, 1, 1) + self.gridLayout_12.addWidget(self.widget_6, 1, 0, 1, 1) + self.widget_7 = QtWidgets.QWidget(self.groupBox) + self.widget_7.setObjectName("widget_7") + self.gridLayout_5 = QtWidgets.QGridLayout(self.widget_7) + self.gridLayout_5.setObjectName("gridLayout_5") + self.radioButton_percentile = QtWidgets.QRadioButton(self.widget_7) + self.radioButton_percentile.setObjectName("radioButton_percentile") + self.buttonGroup.addButton(self.radioButton_percentile) + self.gridLayout_5.addWidget(self.radioButton_percentile, 0, 1, 1, 1) + self.spinBox_percentile_min = QtWidgets.QSpinBox(self.widget_7) + self.spinBox_percentile_min.setProperty("value", 1) + self.spinBox_percentile_min.setObjectName("spinBox_percentile_min") + self.gridLayout_5.addWidget(self.spinBox_percentile_min, 0, 2, 1, 1) + self.spinBox_percentile_max = QtWidgets.QSpinBox(self.widget_7) + self.spinBox_percentile_max.setProperty("value", 99) + self.spinBox_percentile_max.setObjectName("spinBox_percentile_max") + self.gridLayout_5.addWidget(self.spinBox_percentile_max, 0, 3, 1, 1) + self.gridLayout_12.addWidget(self.widget_7, 2, 0, 1, 1) + self.gridLayout_8.addWidget(self.groupBox, 3, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_4, 1, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(self.widget_2) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout_2.addWidget(self.buttonBox, 4, 0, 1, 1) + self.gridLayout.addWidget(self.widget_2, 0, 1, 1, 1) + self.widget_render_window = QtWidgets.QWidget(Dialog) + self.widget_render_window.setObjectName("widget_render_window") + self.gridLayout.addWidget(self.widget_render_window, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox_3.setTitle(_translate("Dialog", "Visualization")) + self.label_5.setText(_translate("Dialog", "Image Number")) + self.groupBox_4.setTitle(_translate("Dialog", "Color")) + self.label_8.setText(_translate("Dialog", "Color Map")) + self.label_9.setText(_translate("Dialog", "NaN Color")) + self.pushButton_nan_color.setText(_translate("Dialog", "Color")) + self.checkBox_logarithmic_color.setText(_translate("Dialog", "Logarithmic color scale")) + self.groupBox.setTitle(_translate("Dialog", "Color Range")) + self.radioButton_default.setText(_translate("Dialog", "Default")) + self.radioButton_absolute.setText(_translate("Dialog", "Absolute")) + self.radioButton_percentile.setText(_translate("Dialog", "Percentile")) diff --git a/view/ui/ui_dialog_export.py b/view/ui/ui_dialog_export.py new file mode 100644 index 0000000..c4f2d6a --- /dev/null +++ b/view/ui/ui_dialog_export.py @@ -0,0 +1,350 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogExport.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogExport(object): + def setupUi(self, DialogExport): + DialogExport.setObjectName("DialogExport") + DialogExport.resize(747, 874) + self.gridLayout = QtWidgets.QGridLayout(DialogExport) + self.gridLayout.setObjectName("gridLayout") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 12, 1, 1, 1) + self.groupBox_additional_features = QtWidgets.QGroupBox(DialogExport) + self.groupBox_additional_features.setObjectName("groupBox_additional_features") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_additional_features) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label_3 = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_3.setObjectName("label_3") + self.gridLayout_4.addWidget(self.label_3, 2, 0, 1, 1) + self.checkBox_show_features = QtWidgets.QCheckBox(self.groupBox_additional_features) + self.checkBox_show_features.setText("") + self.checkBox_show_features.setObjectName("checkBox_show_features") + self.gridLayout_4.addWidget(self.checkBox_show_features, 0, 1, 1, 1) + self.checkBox_show_rag = QtWidgets.QCheckBox(self.groupBox_additional_features) + self.checkBox_show_rag.setText("") + self.checkBox_show_rag.setObjectName("checkBox_show_rag") + self.gridLayout_4.addWidget(self.checkBox_show_rag, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_additional_features) + self.label.setObjectName("label") + self.gridLayout_4.addWidget(self.label, 0, 0, 1, 1) + self.checkBox_show_scale = QtWidgets.QCheckBox(self.groupBox_additional_features) + self.checkBox_show_scale.setText("") + self.checkBox_show_scale.setObjectName("checkBox_show_scale") + self.gridLayout_4.addWidget(self.checkBox_show_scale, 2, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_2.setObjectName("label_2") + self.gridLayout_4.addWidget(self.label_2, 1, 0, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_9 = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_9.setObjectName("label_9") + self.horizontalLayout_3.addWidget(self.label_9) + self.spinBoxColorR = QtWidgets.QSpinBox(self.groupBox_additional_features) + self.spinBoxColorR.setMaximum(255) + self.spinBoxColorR.setObjectName("spinBoxColorR") + self.horizontalLayout_3.addWidget(self.spinBoxColorR) + self.label_11 = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_11.setObjectName("label_11") + self.horizontalLayout_3.addWidget(self.label_11) + self.spinBoxColorG = QtWidgets.QSpinBox(self.groupBox_additional_features) + self.spinBoxColorG.setMaximum(255) + self.spinBoxColorG.setObjectName("spinBoxColorG") + self.horizontalLayout_3.addWidget(self.spinBoxColorG) + self.label_10 = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_10.setObjectName("label_10") + self.horizontalLayout_3.addWidget(self.label_10) + self.spinBoxColorB = QtWidgets.QSpinBox(self.groupBox_additional_features) + self.spinBoxColorB.setMaximum(255) + self.spinBoxColorB.setObjectName("spinBoxColorB") + self.horizontalLayout_3.addWidget(self.spinBoxColorB) + self.gridLayout_4.addLayout(self.horizontalLayout_3, 3, 1, 1, 1) + self.label_background_color = QtWidgets.QLabel(self.groupBox_additional_features) + self.label_background_color.setObjectName("label_background_color") + self.gridLayout_4.addWidget(self.label_background_color, 3, 0, 1, 1) + self.gridLayout.addWidget(self.groupBox_additional_features, 1, 0, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem1, 12, 0, 1, 1) + self.groupBox_general_settings = QtWidgets.QGroupBox(DialogExport) + self.groupBox_general_settings.setObjectName("groupBox_general_settings") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_general_settings) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_fps = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_fps.setObjectName("label_fps") + self.gridLayout_2.addWidget(self.label_fps, 3, 0, 1, 1) + self.label_frame_number = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_frame_number.setObjectName("label_frame_number") + self.gridLayout_2.addWidget(self.label_frame_number, 2, 0, 1, 1) + self.widget = QtWidgets.QWidget(self.groupBox_general_settings) + self.widget.setObjectName("widget") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_3.setObjectName("gridLayout_3") + self.lineEditFilePath = QtWidgets.QLineEdit(self.widget) + self.lineEditFilePath.setObjectName("lineEditFilePath") + self.gridLayout_3.addWidget(self.lineEditFilePath, 0, 0, 1, 1) + self.pushButtonBrowseFilePath = QtWidgets.QPushButton(self.widget) + self.pushButtonBrowseFilePath.setEnabled(True) + self.pushButtonBrowseFilePath.setObjectName("pushButtonBrowseFilePath") + self.gridLayout_3.addWidget(self.pushButtonBrowseFilePath, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.widget, 0, 1, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.spinBoxResolutionX = QtWidgets.QSpinBox(self.groupBox_general_settings) + self.spinBoxResolutionX.setMaximum(10000000) + self.spinBoxResolutionX.setProperty("value", 500) + self.spinBoxResolutionX.setObjectName("spinBoxResolutionX") + self.horizontalLayout_2.addWidget(self.spinBoxResolutionX) + self.label_7 = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_7.setAlignment(QtCore.Qt.AlignCenter) + self.label_7.setObjectName("label_7") + self.horizontalLayout_2.addWidget(self.label_7) + self.spinBoxResolutionY = QtWidgets.QSpinBox(self.groupBox_general_settings) + self.spinBoxResolutionY.setMaximum(100000) + self.spinBoxResolutionY.setProperty("value", 500) + self.spinBoxResolutionY.setObjectName("spinBoxResolutionY") + self.horizontalLayout_2.addWidget(self.spinBoxResolutionY) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 4, 1, 1, 1) + self.label_render_back_end = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_render_back_end.setObjectName("label_render_back_end") + self.gridLayout_2.addWidget(self.label_render_back_end, 1, 0, 1, 1) + self.comboBox_render_back_end = QtWidgets.QComboBox(self.groupBox_general_settings) + self.comboBox_render_back_end.setObjectName("comboBox_render_back_end") + self.gridLayout_2.addWidget(self.comboBox_render_back_end, 1, 1, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.spinBoxStartFrame = QtWidgets.QSpinBox(self.groupBox_general_settings) + self.spinBoxStartFrame.setMaximum(100000) + self.spinBoxStartFrame.setObjectName("spinBoxStartFrame") + self.horizontalLayout.addWidget(self.spinBoxStartFrame) + self.label_to = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_to.setAlignment(QtCore.Qt.AlignCenter) + self.label_to.setObjectName("label_to") + self.horizontalLayout.addWidget(self.label_to) + self.spinBoxEndFrame = QtWidgets.QSpinBox(self.groupBox_general_settings) + self.spinBoxEndFrame.setMaximum(1000000) + self.spinBoxEndFrame.setObjectName("spinBoxEndFrame") + self.horizontalLayout.addWidget(self.spinBoxEndFrame) + self.gridLayout_2.addLayout(self.horizontalLayout, 2, 1, 1, 1) + self.label_resolution = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_resolution.setObjectName("label_resolution") + self.gridLayout_2.addWidget(self.label_resolution, 4, 0, 1, 1) + self.spinBoxFramesPerSecond = QtWidgets.QSpinBox(self.groupBox_general_settings) + self.spinBoxFramesPerSecond.setProperty("value", 1) + self.spinBoxFramesPerSecond.setObjectName("spinBoxFramesPerSecond") + self.gridLayout_2.addWidget(self.spinBoxFramesPerSecond, 3, 1, 1, 1) + self.label_filepath = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_filepath.setObjectName("label_filepath") + self.gridLayout_2.addWidget(self.label_filepath, 0, 0, 1, 1) + self.label_file_format = QtWidgets.QLabel(self.groupBox_general_settings) + self.label_file_format.setObjectName("label_file_format") + self.gridLayout_2.addWidget(self.label_file_format, 5, 0, 1, 1) + self.comboBoxFileFormat = QtWidgets.QComboBox(self.groupBox_general_settings) + self.comboBoxFileFormat.setObjectName("comboBoxFileFormat") + self.comboBoxFileFormat.addItem("") + self.comboBoxFileFormat.addItem("") + self.comboBoxFileFormat.addItem("") + self.comboBoxFileFormat.addItem("") + self.gridLayout_2.addWidget(self.comboBoxFileFormat, 5, 1, 1, 1) + self.gridLayout.addWidget(self.groupBox_general_settings, 0, 0, 1, 1) + self.groupBox_time_stamp_settings = QtWidgets.QGroupBox(DialogExport) + self.groupBox_time_stamp_settings.setObjectName("groupBox_time_stamp_settings") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_time_stamp_settings) + self.gridLayout_6.setObjectName("gridLayout_6") + self.label_15 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_15.setAlignment(QtCore.Qt.AlignCenter) + self.label_15.setObjectName("label_15") + self.gridLayout_6.addWidget(self.label_15, 1, 2, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_8.setObjectName("label_8") + self.gridLayout_6.addWidget(self.label_8, 5, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.groupBox_time_stamp_settings) + self.widget_2.setObjectName("widget_2") + self.gridLayout_5 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_6 = QtWidgets.QLabel(self.widget_2) + self.label_6.setObjectName("label_6") + self.gridLayout_5.addWidget(self.label_6, 0, 3, 1, 1) + self.label_5 = QtWidgets.QLabel(self.widget_2) + self.label_5.setObjectName("label_5") + self.gridLayout_5.addWidget(self.label_5, 0, 1, 1, 1) + self.spinBox_timestamps_blue = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_timestamps_blue.setMaximum(255) + self.spinBox_timestamps_blue.setObjectName("spinBox_timestamps_blue") + self.gridLayout_5.addWidget(self.spinBox_timestamps_blue, 2, 4, 1, 1) + self.label_14 = QtWidgets.QLabel(self.widget_2) + self.label_14.setObjectName("label_14") + self.gridLayout_5.addWidget(self.label_14, 0, 4, 1, 1) + self.spinBox_timestamps_green = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_timestamps_green.setMaximum(255) + self.spinBox_timestamps_green.setObjectName("spinBox_timestamps_green") + self.gridLayout_5.addWidget(self.spinBox_timestamps_green, 2, 3, 1, 1) + self.spinBox_timestamps_red = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_timestamps_red.setMaximum(255) + self.spinBox_timestamps_red.setObjectName("spinBox_timestamps_red") + self.gridLayout_5.addWidget(self.spinBox_timestamps_red, 2, 1, 1, 1) + self.gridLayout_6.addWidget(self.widget_2, 3, 1, 1, 3) + self.spinBox_timestamps_decimals = QtWidgets.QSpinBox(self.groupBox_time_stamp_settings) + self.spinBox_timestamps_decimals.setProperty("value", 4) + self.spinBox_timestamps_decimals.setObjectName("spinBox_timestamps_decimals") + self.gridLayout_6.addWidget(self.spinBox_timestamps_decimals, 1, 3, 1, 1) + self.label_17 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_17.setObjectName("label_17") + self.gridLayout_6.addWidget(self.label_17, 1, 0, 1, 1) + self.label_18 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_18.setAlignment(QtCore.Qt.AlignCenter) + self.label_18.setObjectName("label_18") + self.gridLayout_6.addWidget(self.label_18, 8, 1, 1, 1) + self.comboBox_timestamps_unit = QtWidgets.QComboBox(self.groupBox_time_stamp_settings) + self.comboBox_timestamps_unit.setObjectName("comboBox_timestamps_unit") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.gridLayout_6.addWidget(self.comboBox_timestamps_unit, 5, 1, 1, 1) + self.spinBox_timestamps_pre_decimals = QtWidgets.QSpinBox(self.groupBox_time_stamp_settings) + self.spinBox_timestamps_pre_decimals.setProperty("value", 4) + self.spinBox_timestamps_pre_decimals.setObjectName("spinBox_timestamps_pre_decimals") + self.gridLayout_6.addWidget(self.spinBox_timestamps_pre_decimals, 1, 1, 1, 1) + self.doubleSpinBox_start_time = QtWidgets.QDoubleSpinBox(self.groupBox_time_stamp_settings) + self.doubleSpinBox_start_time.setDecimals(4) + self.doubleSpinBox_start_time.setMinimum(-100000.0) + self.doubleSpinBox_start_time.setMaximum(100000.0) + self.doubleSpinBox_start_time.setObjectName("doubleSpinBox_start_time") + self.gridLayout_6.addWidget(self.doubleSpinBox_start_time, 4, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_16.setObjectName("label_16") + self.gridLayout_6.addWidget(self.label_16, 3, 0, 1, 1) + self.label_12 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_12.setObjectName("label_12") + self.gridLayout_6.addWidget(self.label_12, 6, 0, 1, 1) + self.label_13 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_13.setObjectName("label_13") + self.gridLayout_6.addWidget(self.label_13, 4, 0, 1, 1) + self.label_19 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_19.setAlignment(QtCore.Qt.AlignCenter) + self.label_19.setObjectName("label_19") + self.gridLayout_6.addWidget(self.label_19, 8, 2, 1, 1) + self.label_21 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_21.setObjectName("label_21") + self.gridLayout_6.addWidget(self.label_21, 7, 0, 1, 1) + self.spinBox_timestamps_position_x = QtWidgets.QSpinBox(self.groupBox_time_stamp_settings) + self.spinBox_timestamps_position_x.setMaximum(10000) + self.spinBox_timestamps_position_x.setObjectName("spinBox_timestamps_position_x") + self.gridLayout_6.addWidget(self.spinBox_timestamps_position_x, 9, 1, 1, 1) + self.widget_vtk_text_actor = QtWidgets.QWidget(self.groupBox_time_stamp_settings) + self.widget_vtk_text_actor.setObjectName("widget_vtk_text_actor") + self.gridLayout_6.addWidget(self.widget_vtk_text_actor, 10, 0, 1, 4) + self.checkBox_timestamps_show = QtWidgets.QCheckBox(self.groupBox_time_stamp_settings) + self.checkBox_timestamps_show.setText("") + self.checkBox_timestamps_show.setChecked(True) + self.checkBox_timestamps_show.setObjectName("checkBox_timestamps_show") + self.gridLayout_6.addWidget(self.checkBox_timestamps_show, 6, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_4.setObjectName("label_4") + self.gridLayout_6.addWidget(self.label_4, 2, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_20.setObjectName("label_20") + self.gridLayout_6.addWidget(self.label_20, 9, 0, 1, 1) + self.spinBox_timestamps_position_y = QtWidgets.QSpinBox(self.groupBox_time_stamp_settings) + self.spinBox_timestamps_position_y.setMaximum(10000) + self.spinBox_timestamps_position_y.setObjectName("spinBox_timestamps_position_y") + self.gridLayout_6.addWidget(self.spinBox_timestamps_position_y, 9, 2, 1, 1) + self.checkBox_show_scan_direction = QtWidgets.QCheckBox(self.groupBox_time_stamp_settings) + self.checkBox_show_scan_direction.setText("") + self.checkBox_show_scan_direction.setChecked(True) + self.checkBox_show_scan_direction.setObjectName("checkBox_show_scan_direction") + self.gridLayout_6.addWidget(self.checkBox_show_scan_direction, 7, 1, 1, 1) + self.spinBox_timestamps_text_font_size = QtWidgets.QSpinBox(self.groupBox_time_stamp_settings) + self.spinBox_timestamps_text_font_size.setProperty("value", 18) + self.spinBox_timestamps_text_font_size.setObjectName("spinBox_timestamps_text_font_size") + self.gridLayout_6.addWidget(self.spinBox_timestamps_text_font_size, 2, 1, 1, 1) + self.label_22 = QtWidgets.QLabel(self.groupBox_time_stamp_settings) + self.label_22.setObjectName("label_22") + self.gridLayout_6.addWidget(self.label_22, 0, 0, 1, 1) + self.lineEdit_pre_text = QtWidgets.QLineEdit(self.groupBox_time_stamp_settings) + self.lineEdit_pre_text.setObjectName("lineEdit_pre_text") + self.gridLayout_6.addWidget(self.lineEdit_pre_text, 0, 1, 1, 1) + self.pushButton_set_start_frame_time_stamp_to_zero = QtWidgets.QPushButton(self.groupBox_time_stamp_settings) + self.pushButton_set_start_frame_time_stamp_to_zero.setObjectName("pushButton_set_start_frame_time_stamp_to_zero") + self.gridLayout_6.addWidget(self.pushButton_set_start_frame_time_stamp_to_zero, 4, 2, 1, 2) + self.gridLayout.addWidget(self.groupBox_time_stamp_settings, 8, 0, 1, 1) + self.pushButton_show_preview = QtWidgets.QPushButton(DialogExport) + self.pushButton_show_preview.setObjectName("pushButton_show_preview") + self.gridLayout.addWidget(self.pushButton_show_preview, 10, 0, 1, 2) + self.buttonBoxExport = QtWidgets.QDialogButtonBox(DialogExport) + self.buttonBoxExport.setOrientation(QtCore.Qt.Horizontal) + self.buttonBoxExport.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBoxExport.setObjectName("buttonBoxExport") + self.gridLayout.addWidget(self.buttonBoxExport, 11, 0, 1, 1) + self.label_message = QtWidgets.QLabel(DialogExport) + self.label_message.setText("") + self.label_message.setObjectName("label_message") + self.gridLayout.addWidget(self.label_message, 9, 0, 1, 1) + + self.retranslateUi(DialogExport) + self.comboBox_timestamps_unit.setCurrentIndex(1) + self.buttonBoxExport.accepted.connect(DialogExport.accept) + self.buttonBoxExport.rejected.connect(DialogExport.reject) + QtCore.QMetaObject.connectSlotsByName(DialogExport) + DialogExport.setTabOrder(self.spinBoxStartFrame, self.spinBoxEndFrame) + DialogExport.setTabOrder(self.spinBoxEndFrame, self.spinBoxResolutionX) + DialogExport.setTabOrder(self.spinBoxResolutionX, self.spinBoxResolutionY) + DialogExport.setTabOrder(self.spinBoxResolutionY, self.spinBoxColorR) + DialogExport.setTabOrder(self.spinBoxColorR, self.spinBoxColorG) + DialogExport.setTabOrder(self.spinBoxColorG, self.spinBoxColorB) + + def retranslateUi(self, DialogExport): + _translate = QtCore.QCoreApplication.translate + DialogExport.setWindowTitle(_translate("DialogExport", "Export as Video")) + self.groupBox_additional_features.setTitle(_translate("DialogExport", "Additional Features")) + self.label_3.setText(_translate("DialogExport", "Show Scale")) + self.label.setText(_translate("DialogExport", "Show Features")) + self.label_2.setText(_translate("DialogExport", "Show Rag")) + self.label_9.setText(_translate("DialogExport", "R")) + self.label_11.setText(_translate("DialogExport", "G")) + self.label_10.setText(_translate("DialogExport", "B")) + self.label_background_color.setText(_translate("DialogExport", "Background color")) + self.groupBox_general_settings.setTitle(_translate("DialogExport", "General Settings")) + self.label_fps.setText(_translate("DialogExport", "Frames per second")) + self.label_frame_number.setText(_translate("DialogExport", "From Frame")) + self.pushButtonBrowseFilePath.setText(_translate("DialogExport", "Browse Filesystem")) + self.label_7.setText(_translate("DialogExport", "x")) + self.label_render_back_end.setText(_translate("DialogExport", "Render Backend")) + self.label_to.setText(_translate("DialogExport", "to")) + self.label_resolution.setText(_translate("DialogExport", "Resolution")) + self.label_filepath.setText(_translate("DialogExport", "filepath:")) + self.label_file_format.setText(_translate("DialogExport", "File format")) + self.comboBoxFileFormat.setItemText(0, _translate("DialogExport", ".ogv")) + self.comboBoxFileFormat.setItemText(1, _translate("DialogExport", ".mp4")) + self.comboBoxFileFormat.setItemText(2, _translate("DialogExport", ".avi")) + self.comboBoxFileFormat.setItemText(3, _translate("DialogExport", ".gif")) + self.groupBox_time_stamp_settings.setTitle(_translate("DialogExport", "Time Stamp Settings")) + self.label_15.setText(_translate("DialogExport", ",")) + self.label_8.setText(_translate("DialogExport", "Unit")) + self.label_6.setText(_translate("DialogExport", "Green")) + self.label_5.setText(_translate("DialogExport", "Red")) + self.label_14.setText(_translate("DialogExport", "Blue")) + self.label_17.setText(_translate("DialogExport", "Digits")) + self.label_18.setText(_translate("DialogExport", "X")) + self.comboBox_timestamps_unit.setItemText(0, _translate("DialogExport", "minutes")) + self.comboBox_timestamps_unit.setItemText(1, _translate("DialogExport", "seconds")) + self.comboBox_timestamps_unit.setItemText(2, _translate("DialogExport", "milliseconds")) + self.comboBox_timestamps_unit.setItemText(3, _translate("DialogExport", "microseconds")) + self.label_16.setText(_translate("DialogExport", "Color")) + self.label_12.setText(_translate("DialogExport", "Show Time Stamps")) + self.label_13.setText(_translate("DialogExport", "Start Time")) + self.label_19.setText(_translate("DialogExport", "Y")) + self.label_21.setText(_translate("DialogExport", "Show Scan Direction")) + self.label_4.setText(_translate("DialogExport", "Font Size")) + self.label_20.setText(_translate("DialogExport", "Text Position")) + self.label_22.setText(_translate("DialogExport", "Text")) + self.pushButton_set_start_frame_time_stamp_to_zero.setText(_translate("DialogExport", "Start Frame to 0")) + self.pushButton_show_preview.setText(_translate("DialogExport", "Show Preview")) diff --git a/view/ui/ui_dialog_export_xyz.py b/view/ui/ui_dialog_export_xyz.py new file mode 100644 index 0000000..cc9a527 --- /dev/null +++ b/view/ui/ui_dialog_export_xyz.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogExportXYZ.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.doubleSpinBox_x_direction_stretch = QtWidgets.QDoubleSpinBox(self.widget) + self.doubleSpinBox_x_direction_stretch.setDecimals(10) + self.doubleSpinBox_x_direction_stretch.setMinimum(-100000000000000.0) + self.doubleSpinBox_x_direction_stretch.setMaximum(10000000000.0) + self.doubleSpinBox_x_direction_stretch.setObjectName("doubleSpinBox_x_direction_stretch") + self.gridLayout_2.addWidget(self.doubleSpinBox_x_direction_stretch, 2, 1, 1, 1) + self.doubleSpinBox_y_direction_stretch = QtWidgets.QDoubleSpinBox(self.widget) + self.doubleSpinBox_y_direction_stretch.setDecimals(10) + self.doubleSpinBox_y_direction_stretch.setMinimum(-100000000000000.0) + self.doubleSpinBox_y_direction_stretch.setMaximum(10000000000.0) + self.doubleSpinBox_y_direction_stretch.setObjectName("doubleSpinBox_y_direction_stretch") + self.gridLayout_2.addWidget(self.doubleSpinBox_y_direction_stretch, 3, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 3, 0, 1, 1) + self.lineEdit_comment = QtWidgets.QLineEdit(self.widget) + self.lineEdit_comment.setObjectName("lineEdit_comment") + self.gridLayout_2.addWidget(self.lineEdit_comment, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.lineEdit_file_path = QtWidgets.QLineEdit(self.widget_2) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.horizontalLayout.addWidget(self.lineEdit_file_path) + self.pushButton_browse_file_system = QtWidgets.QPushButton(self.widget_2) + self.pushButton_browse_file_system.setObjectName("pushButton_browse_file_system") + self.horizontalLayout.addWidget(self.pushButton_browse_file_system) + self.gridLayout_2.addWidget(self.widget_2, 0, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.widget) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.widget) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 1, 0, 1, 1) + self.pushButton_auto_scale_to_angstrom = QtWidgets.QPushButton(self.widget) + self.pushButton_auto_scale_to_angstrom.setObjectName("pushButton_auto_scale_to_angstrom") + self.gridLayout_2.addWidget(self.pushButton_auto_scale_to_angstrom, 4, 0, 1, 2) + self.gridLayout.addWidget(self.widget, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Export as XYZ")) + self.label_2.setText(_translate("Dialog", "Y-Direction Stretch")) + self.label.setText(_translate("Dialog", "X-Direction Stretch")) + self.pushButton_browse_file_system.setText(_translate("Dialog", "Browse")) + self.label_3.setText(_translate("Dialog", "File Path")) + self.label_4.setText(_translate("Dialog", "Comment")) + self.pushButton_auto_scale_to_angstrom.setText(_translate("Dialog", "Set to Angstrom")) diff --git a/view/ui/ui_dialog_export_zip.py b/view/ui/ui_dialog_export_zip.py new file mode 100644 index 0000000..c16bf89 --- /dev/null +++ b/view/ui/ui_dialog_export_zip.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogExportZip.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogExportZip(object): + def setupUi(self, DialogExportZip): + DialogExportZip.setObjectName("DialogExportZip") + DialogExportZip.resize(1020, 938) + self.gridLayout = QtWidgets.QGridLayout(DialogExportZip) + self.gridLayout.setObjectName("gridLayout") + self.scrollArea = QtWidgets.QScrollArea(DialogExportZip) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1000, 918)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.gridLayout_9 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents) + self.gridLayout_9.setObjectName("gridLayout_9") + self.label_filepath = QtWidgets.QLabel(self.scrollAreaWidgetContents) + self.label_filepath.setObjectName("label_filepath") + self.gridLayout_9.addWidget(self.label_filepath, 0, 0, 1, 1) + self.lineEditFilePath = QtWidgets.QLineEdit(self.scrollAreaWidgetContents) + self.lineEditFilePath.setObjectName("lineEditFilePath") + self.gridLayout_9.addWidget(self.lineEditFilePath, 0, 1, 1, 1) + self.pushButtonBrowseFilePath = QtWidgets.QPushButton(self.scrollAreaWidgetContents) + self.pushButtonBrowseFilePath.setEnabled(True) + self.pushButtonBrowseFilePath.setObjectName("pushButtonBrowseFilePath") + self.gridLayout_9.addWidget(self.pushButtonBrowseFilePath, 0, 2, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_9 = QtWidgets.QLabel(self.groupBox) + self.label_9.setAlignment(QtCore.Qt.AlignCenter) + self.label_9.setObjectName("label_9") + self.horizontalLayout_3.addWidget(self.label_9) + self.spinBoxColorR = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxColorR.setMaximum(255) + self.spinBoxColorR.setObjectName("spinBoxColorR") + self.horizontalLayout_3.addWidget(self.spinBoxColorR) + self.label_11 = QtWidgets.QLabel(self.groupBox) + self.label_11.setAlignment(QtCore.Qt.AlignCenter) + self.label_11.setObjectName("label_11") + self.horizontalLayout_3.addWidget(self.label_11) + self.spinBoxColorG = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxColorG.setMaximum(255) + self.spinBoxColorG.setObjectName("spinBoxColorG") + self.horizontalLayout_3.addWidget(self.spinBoxColorG) + self.label_10 = QtWidgets.QLabel(self.groupBox) + self.label_10.setAlignment(QtCore.Qt.AlignCenter) + self.label_10.setObjectName("label_10") + self.horizontalLayout_3.addWidget(self.label_10) + self.spinBoxColorB = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxColorB.setMaximum(255) + self.spinBoxColorB.setObjectName("spinBoxColorB") + self.horizontalLayout_3.addWidget(self.spinBoxColorB) + self.gridLayout_2.addLayout(self.horizontalLayout_3, 4, 1, 1, 5) + self.label_background_color = QtWidgets.QLabel(self.groupBox) + self.label_background_color.setObjectName("label_background_color") + self.gridLayout_2.addWidget(self.label_background_color, 4, 0, 1, 1) + self.label_fps = QtWidgets.QLabel(self.groupBox) + self.label_fps.setObjectName("label_fps") + self.gridLayout_2.addWidget(self.label_fps, 1, 0, 1, 1) + self.comboBoxFileFormat = QtWidgets.QComboBox(self.groupBox) + self.comboBoxFileFormat.setObjectName("comboBoxFileFormat") + self.comboBoxFileFormat.addItem("") + self.comboBoxFileFormat.addItem("") + self.comboBoxFileFormat.addItem("") + self.gridLayout_2.addWidget(self.comboBoxFileFormat, 3, 1, 1, 5) + self.spinBoxFramesPerSecond = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxFramesPerSecond.setProperty("value", 20) + self.spinBoxFramesPerSecond.setObjectName("spinBoxFramesPerSecond") + self.gridLayout_2.addWidget(self.spinBoxFramesPerSecond, 1, 1, 1, 5) + self.label_resolution = QtWidgets.QLabel(self.groupBox) + self.label_resolution.setObjectName("label_resolution") + self.gridLayout_2.addWidget(self.label_resolution, 2, 0, 1, 1) + self.label_file_format = QtWidgets.QLabel(self.groupBox) + self.label_file_format.setObjectName("label_file_format") + self.gridLayout_2.addWidget(self.label_file_format, 3, 0, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox) + self.label_7.setAlignment(QtCore.Qt.AlignCenter) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 2, 3, 1, 2) + self.spinBoxResolutionY = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxResolutionY.setMaximum(100000) + self.spinBoxResolutionY.setProperty("value", 500) + self.spinBoxResolutionY.setObjectName("spinBoxResolutionY") + self.gridLayout_2.addWidget(self.spinBoxResolutionY, 2, 5, 1, 1) + self.spinBoxResolutionX = QtWidgets.QSpinBox(self.groupBox) + self.spinBoxResolutionX.setMaximum(10000000) + self.spinBoxResolutionX.setProperty("value", 500) + self.spinBoxResolutionX.setObjectName("spinBoxResolutionX") + self.gridLayout_2.addWidget(self.spinBoxResolutionX, 2, 1, 1, 2) + self.checkBox_export_video = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_export_video.setChecked(True) + self.checkBox_export_video.setObjectName("checkBox_export_video") + self.gridLayout_2.addWidget(self.checkBox_export_video, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox, 1, 0, 1, 3) + self.groupBox_2 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.checkBox_export_vsy = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_export_vsy.setChecked(True) + self.checkBox_export_vsy.setObjectName("checkBox_export_vsy") + self.gridLayout_3.addWidget(self.checkBox_export_vsy, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox_2, 2, 0, 1, 3) + self.groupBox_6 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_6.setObjectName("groupBox_6") + self.gridLayout_7 = QtWidgets.QGridLayout(self.groupBox_6) + self.gridLayout_7.setObjectName("gridLayout_7") + self.checkBox_export_original = QtWidgets.QCheckBox(self.groupBox_6) + self.checkBox_export_original.setChecked(True) + self.checkBox_export_original.setObjectName("checkBox_export_original") + self.gridLayout_7.addWidget(self.checkBox_export_original, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox_6, 3, 0, 1, 3) + self.groupBox_5 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_6.setObjectName("gridLayout_6") + self.checkBox_export_config = QtWidgets.QCheckBox(self.groupBox_5) + self.checkBox_export_config.setChecked(True) + self.checkBox_export_config.setObjectName("checkBox_export_config") + self.gridLayout_6.addWidget(self.checkBox_export_config, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox_5, 4, 0, 1, 3) + self.groupBox_3 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.checkBox_export_filter_logs = QtWidgets.QCheckBox(self.groupBox_3) + self.checkBox_export_filter_logs.setChecked(True) + self.checkBox_export_filter_logs.setObjectName("checkBox_export_filter_logs") + self.gridLayout_4.addWidget(self.checkBox_export_filter_logs, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox_3, 5, 0, 1, 3) + self.groupBox_4 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_5.setObjectName("gridLayout_5") + self.checkBox_export_feature_list = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_export_feature_list.setChecked(True) + self.checkBox_export_feature_list.setObjectName("checkBox_export_feature_list") + self.gridLayout_5.addWidget(self.checkBox_export_feature_list, 0, 0, 1, 1) + self.gridLayout_9.addWidget(self.groupBox_4, 6, 0, 1, 3) + self.groupBox_7 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_7.setObjectName("groupBox_7") + self.gridLayout_8 = QtWidgets.QGridLayout(self.groupBox_7) + self.gridLayout_8.setObjectName("gridLayout_8") + self.label = QtWidgets.QLabel(self.groupBox_7) + self.label.setObjectName("label") + self.gridLayout_8.addWidget(self.label, 1, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox_7) + self.label_2.setObjectName("label_2") + self.gridLayout_8.addWidget(self.label_2, 2, 0, 1, 1) + self.spinBox_image_resolution_y = QtWidgets.QSpinBox(self.groupBox_7) + self.spinBox_image_resolution_y.setMaximum(10000000) + self.spinBox_image_resolution_y.setProperty("value", 500) + self.spinBox_image_resolution_y.setObjectName("spinBox_image_resolution_y") + self.gridLayout_8.addWidget(self.spinBox_image_resolution_y, 1, 3, 1, 1) + self.label_3 = QtWidgets.QLabel(self.groupBox_7) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) + self.label_3.setObjectName("label_3") + self.gridLayout_8.addWidget(self.label_3, 1, 2, 1, 1) + self.comboBox_images_file_format = QtWidgets.QComboBox(self.groupBox_7) + self.comboBox_images_file_format.setObjectName("comboBox_images_file_format") + self.comboBox_images_file_format.addItem("") + self.comboBox_images_file_format.addItem("") + self.gridLayout_8.addWidget(self.comboBox_images_file_format, 2, 1, 1, 1) + self.spinBox_image_resolution_x = QtWidgets.QSpinBox(self.groupBox_7) + self.spinBox_image_resolution_x.setMaximum(10000000) + self.spinBox_image_resolution_x.setProperty("value", 500) + self.spinBox_image_resolution_x.setObjectName("spinBox_image_resolution_x") + self.gridLayout_8.addWidget(self.spinBox_image_resolution_x, 1, 1, 1, 1) + self.checkBox_export_images = QtWidgets.QCheckBox(self.groupBox_7) + self.checkBox_export_images.setObjectName("checkBox_export_images") + self.gridLayout_8.addWidget(self.checkBox_export_images, 0, 0, 1, 4) + self.label_4 = QtWidgets.QLabel(self.groupBox_7) + self.label_4.setObjectName("label_4") + self.gridLayout_8.addWidget(self.label_4, 3, 0, 1, 1) + self.widget = QtWidgets.QWidget(self.groupBox_7) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setAlignment(QtCore.Qt.AlignCenter) + self.label_5.setObjectName("label_5") + self.horizontalLayout.addWidget(self.label_5) + self.spinBox_images_background_color_r = QtWidgets.QSpinBox(self.widget) + self.spinBox_images_background_color_r.setMaximum(255) + self.spinBox_images_background_color_r.setObjectName("spinBox_images_background_color_r") + self.horizontalLayout.addWidget(self.spinBox_images_background_color_r) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setAlignment(QtCore.Qt.AlignCenter) + self.label_6.setObjectName("label_6") + self.horizontalLayout.addWidget(self.label_6) + self.spinBox_images_background_color_g = QtWidgets.QSpinBox(self.widget) + self.spinBox_images_background_color_g.setMaximum(255) + self.spinBox_images_background_color_g.setObjectName("spinBox_images_background_color_g") + self.horizontalLayout.addWidget(self.spinBox_images_background_color_g) + self.label_8 = QtWidgets.QLabel(self.widget) + self.label_8.setAlignment(QtCore.Qt.AlignCenter) + self.label_8.setObjectName("label_8") + self.horizontalLayout.addWidget(self.label_8) + self.spinBox_images_background_color_b = QtWidgets.QSpinBox(self.widget) + self.spinBox_images_background_color_b.setMaximum(255) + self.spinBox_images_background_color_b.setObjectName("spinBox_images_background_color_b") + self.horizontalLayout.addWidget(self.spinBox_images_background_color_b) + self.gridLayout_8.addWidget(self.widget, 3, 1, 1, 3) + self.gridLayout_9.addWidget(self.groupBox_7, 7, 0, 1, 3) + self.buttonBoxExport = QtWidgets.QDialogButtonBox(self.scrollAreaWidgetContents) + self.buttonBoxExport.setOrientation(QtCore.Qt.Horizontal) + self.buttonBoxExport.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBoxExport.setObjectName("buttonBoxExport") + self.gridLayout_9.addWidget(self.buttonBoxExport, 8, 2, 1, 1) + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.gridLayout.addWidget(self.scrollArea, 0, 0, 1, 1) + + self.retranslateUi(DialogExportZip) + self.buttonBoxExport.accepted.connect(DialogExportZip.accept) + self.buttonBoxExport.rejected.connect(DialogExportZip.reject) + QtCore.QMetaObject.connectSlotsByName(DialogExportZip) + DialogExportZip.setTabOrder(self.spinBoxColorR, self.spinBoxColorG) + DialogExportZip.setTabOrder(self.spinBoxColorG, self.spinBoxColorB) + + def retranslateUi(self, DialogExportZip): + _translate = QtCore.QCoreApplication.translate + DialogExportZip.setWindowTitle(_translate("DialogExportZip", "Export as Zip")) + self.label_filepath.setText(_translate("DialogExportZip", "filepath:")) + self.pushButtonBrowseFilePath.setText(_translate("DialogExportZip", "Browse Filesystem")) + self.groupBox.setTitle(_translate("DialogExportZip", "Export Video")) + self.label_9.setText(_translate("DialogExportZip", "R")) + self.label_11.setText(_translate("DialogExportZip", "G")) + self.label_10.setText(_translate("DialogExportZip", "B")) + self.label_background_color.setText(_translate("DialogExportZip", "Background Color")) + self.label_fps.setText(_translate("DialogExportZip", "Frames per Second")) + self.comboBoxFileFormat.setItemText(0, _translate("DialogExportZip", ".ogv")) + self.comboBoxFileFormat.setItemText(1, _translate("DialogExportZip", ".mp4")) + self.comboBoxFileFormat.setItemText(2, _translate("DialogExportZip", ".avi")) + self.label_resolution.setText(_translate("DialogExportZip", "Resolution")) + self.label_file_format.setText(_translate("DialogExportZip", "File Format")) + self.label_7.setText(_translate("DialogExportZip", "x")) + self.checkBox_export_video.setText(_translate("DialogExportZip", "Export Video")) + self.groupBox_2.setTitle(_translate("DialogExportZip", "Export Vsy")) + self.checkBox_export_vsy.setText(_translate("DialogExportZip", "Export Visualyse File")) + self.groupBox_6.setTitle(_translate("DialogExportZip", "Export Original File")) + self.checkBox_export_original.setText(_translate("DialogExportZip", "Export Original File")) + self.groupBox_5.setTitle(_translate("DialogExportZip", "Export Config")) + self.checkBox_export_config.setText(_translate("DialogExportZip", "Export Config File")) + self.groupBox_3.setTitle(_translate("DialogExportZip", "Export Filter Log File")) + self.checkBox_export_filter_logs.setText(_translate("DialogExportZip", "Export Filter Logs")) + self.groupBox_4.setTitle(_translate("DialogExportZip", "Export Feature List")) + self.checkBox_export_feature_list.setText(_translate("DialogExportZip", "Export Feature List")) + self.groupBox_7.setTitle(_translate("DialogExportZip", "Export Images")) + self.label.setText(_translate("DialogExportZip", "Resolution")) + self.label_2.setText(_translate("DialogExportZip", "File Format")) + self.label_3.setText(_translate("DialogExportZip", "x")) + self.comboBox_images_file_format.setItemText(0, _translate("DialogExportZip", ".png")) + self.comboBox_images_file_format.setItemText(1, _translate("DialogExportZip", ".jpg")) + self.checkBox_export_images.setText(_translate("DialogExportZip", "Export Images")) + self.label_4.setText(_translate("DialogExportZip", "Background Color")) + self.label_5.setText(_translate("DialogExportZip", "R")) + self.label_6.setText(_translate("DialogExportZip", "G")) + self.label_8.setText(_translate("DialogExportZip", "B")) diff --git a/view/ui/ui_dialog_feature_class_visualization.py b/view/ui/ui_dialog_feature_class_visualization.py new file mode 100644 index 0000000..97d3a6c --- /dev/null +++ b/view/ui/ui_dialog_feature_class_visualization.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFeatureClassVisualization.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogFeatureClassVisualization(object): + def setupUi(self, DialogFeatureClassVisualization): + DialogFeatureClassVisualization.setObjectName("DialogFeatureClassVisualization") + DialogFeatureClassVisualization.resize(554, 240) + self.gridLayout = QtWidgets.QGridLayout(DialogFeatureClassVisualization) + self.gridLayout.setObjectName("gridLayout") + self.label_feature_class_name = QtWidgets.QLabel(DialogFeatureClassVisualization) + self.label_feature_class_name.setText("") + self.label_feature_class_name.setObjectName("label_feature_class_name") + self.gridLayout.addWidget(self.label_feature_class_name, 0, 0, 1, 1) + self.groupBox_4 = QtWidgets.QGroupBox(DialogFeatureClassVisualization) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_33 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_33.setObjectName("gridLayout_33") + self.label_32 = QtWidgets.QLabel(self.groupBox_4) + self.label_32.setObjectName("label_32") + self.gridLayout_33.addWidget(self.label_32, 0, 0, 1, 1) + self.doubleSpinBox_feature_thickness = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_feature_thickness.setObjectName("doubleSpinBox_feature_thickness") + self.gridLayout_33.addWidget(self.doubleSpinBox_feature_thickness, 2, 1, 1, 1) + self.comboBox_feature_representation = QtWidgets.QComboBox(self.groupBox_4) + self.comboBox_feature_representation.setObjectName("comboBox_feature_representation") + self.comboBox_feature_representation.addItem("") + self.comboBox_feature_representation.addItem("") + self.comboBox_feature_representation.addItem("") + self.gridLayout_33.addWidget(self.comboBox_feature_representation, 0, 1, 1, 1) + self.label_33 = QtWidgets.QLabel(self.groupBox_4) + self.label_33.setObjectName("label_33") + self.gridLayout_33.addWidget(self.label_33, 1, 0, 1, 1) + self.doubleSpinBox_feature_point_size = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_feature_point_size.setDecimals(4) + self.doubleSpinBox_feature_point_size.setMaximum(10000000000.0) + self.doubleSpinBox_feature_point_size.setObjectName("doubleSpinBox_feature_point_size") + self.gridLayout_33.addWidget(self.doubleSpinBox_feature_point_size, 1, 1, 1, 1) + self.label_34 = QtWidgets.QLabel(self.groupBox_4) + self.label_34.setObjectName("label_34") + self.gridLayout_33.addWidget(self.label_34, 2, 0, 1, 1) + self.gridLayout.addWidget(self.groupBox_4, 1, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(DialogFeatureClassVisualization) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1) + + self.retranslateUi(DialogFeatureClassVisualization) + self.buttonBox.accepted.connect(DialogFeatureClassVisualization.accept) + self.buttonBox.rejected.connect(DialogFeatureClassVisualization.reject) + QtCore.QMetaObject.connectSlotsByName(DialogFeatureClassVisualization) + + def retranslateUi(self, DialogFeatureClassVisualization): + _translate = QtCore.QCoreApplication.translate + DialogFeatureClassVisualization.setWindowTitle(_translate("DialogFeatureClassVisualization", "Feature Class Visualization Dialog")) + self.groupBox_4.setTitle(_translate("DialogFeatureClassVisualization", "Feature Representation")) + self.label_32.setText(_translate("DialogFeatureClassVisualization", "Feature Representation")) + self.comboBox_feature_representation.setItemText(0, _translate("DialogFeatureClassVisualization", "Disk")) + self.comboBox_feature_representation.setItemText(1, _translate("DialogFeatureClassVisualization", "Circle")) + self.comboBox_feature_representation.setItemText(2, _translate("DialogFeatureClassVisualization", "Sphere")) + self.label_33.setText(_translate("DialogFeatureClassVisualization", "Feature Point Size (in image Pixel)")) + self.label_34.setText(_translate("DialogFeatureClassVisualization", "Feature Thickness (in Percent)")) diff --git a/view/ui/ui_dialog_feature_grid.py b/view/ui/ui_dialog_feature_grid.py new file mode 100644 index 0000000..6f245f1 --- /dev/null +++ b/view/ui/ui_dialog_feature_grid.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFeatureGrid.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FeatureGridDialog(object): + def setupUi(self, FeatureGridDialog): + FeatureGridDialog.setObjectName("FeatureGridDialog") + FeatureGridDialog.resize(844, 639) + self.gridLayout = QtWidgets.QGridLayout(FeatureGridDialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_render_window = QtWidgets.QWidget(FeatureGridDialog) + self.widget_render_window.setMinimumSize(QtCore.QSize(400, 400)) + self.widget_render_window.setObjectName("widget_render_window") + self.gridLayout.addWidget(self.widget_render_window, 0, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(FeatureGridDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_2.setObjectName("gridLayout_2") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 2, 0, 1, 1) + self.groupBox_settings_feature_detection = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_settings_feature_detection.setObjectName("groupBox_settings_feature_detection") + self.gridLayout_38 = QtWidgets.QGridLayout(self.groupBox_settings_feature_detection) + self.gridLayout_38.setObjectName("gridLayout_38") + self.comboBox_feature_auto_detect_function = QtWidgets.QComboBox(self.groupBox_settings_feature_detection) + self.comboBox_feature_auto_detect_function.setObjectName("comboBox_feature_auto_detect_function") + self.gridLayout_38.addWidget(self.comboBox_feature_auto_detect_function, 0, 1, 1, 1) + self.label_47 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_47.setObjectName("label_47") + self.gridLayout_38.addWidget(self.label_47, 6, 0, 1, 1) + self.label_42 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_42.setObjectName("label_42") + self.gridLayout_38.addWidget(self.label_42, 2, 0, 1, 1) + self.label_43 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_43.setObjectName("label_43") + self.gridLayout_38.addWidget(self.label_43, 0, 0, 1, 1) + self.checkBox_feature_auto_detect_detect_minima = QtWidgets.QCheckBox(self.groupBox_settings_feature_detection) + self.checkBox_feature_auto_detect_detect_minima.setText("") + self.checkBox_feature_auto_detect_detect_minima.setObjectName("checkBox_feature_auto_detect_detect_minima") + self.gridLayout_38.addWidget(self.checkBox_feature_auto_detect_detect_minima, 1, 1, 1, 1) + self.spinBox_feature_auto_detect_num_sigma = QtWidgets.QSpinBox(self.groupBox_settings_feature_detection) + self.spinBox_feature_auto_detect_num_sigma.setObjectName("spinBox_feature_auto_detect_num_sigma") + self.gridLayout_38.addWidget(self.spinBox_feature_auto_detect_num_sigma, 4, 1, 1, 1) + self.doubleSpinBox_feature_auto_detect_overlap = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_overlap.setObjectName("doubleSpinBox_feature_auto_detect_overlap") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_overlap, 6, 1, 1, 1) + self.label_45 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_45.setObjectName("label_45") + self.gridLayout_38.addWidget(self.label_45, 4, 0, 1, 1) + self.label_44 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_44.setObjectName("label_44") + self.gridLayout_38.addWidget(self.label_44, 3, 0, 1, 1) + self.label_46 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_46.setObjectName("label_46") + self.gridLayout_38.addWidget(self.label_46, 5, 0, 1, 1) + self.doubleSpinBox_feature_auto_detect_min_sigma = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_min_sigma.setObjectName("doubleSpinBox_feature_auto_detect_min_sigma") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_min_sigma, 2, 1, 1, 1) + self.doubleSpinBox_feature_auto_detect_threshold = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_threshold.setObjectName("doubleSpinBox_feature_auto_detect_threshold") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_threshold, 5, 1, 1, 1) + self.label_59 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_59.setObjectName("label_59") + self.gridLayout_38.addWidget(self.label_59, 1, 0, 1, 1) + self.doubleSpinBox_feature_auto_detect_max_sigma = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_max_sigma.setObjectName("doubleSpinBox_feature_auto_detect_max_sigma") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_max_sigma, 3, 1, 1, 1) + self.pushButton_detect_grid_points = QtWidgets.QPushButton(self.groupBox_settings_feature_detection) + self.pushButton_detect_grid_points.setObjectName("pushButton_detect_grid_points") + self.gridLayout_38.addWidget(self.pushButton_detect_grid_points, 7, 0, 1, 2) + self.gridLayout_2.addWidget(self.groupBox_settings_feature_detection, 1, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.widget_2) + self.groupBox.setObjectName("groupBox") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_3.setObjectName("gridLayout_3") + self.doubleSpinBox_point_size = QtWidgets.QDoubleSpinBox(self.groupBox) + self.doubleSpinBox_point_size.setMaximum(50000.0) + self.doubleSpinBox_point_size.setObjectName("doubleSpinBox_point_size") + self.gridLayout_3.addWidget(self.doubleSpinBox_point_size, 0, 1, 1, 1) + self.pushButton_color_selector = QtWidgets.QPushButton(self.groupBox) + self.pushButton_color_selector.setObjectName("pushButton_color_selector") + self.gridLayout_3.addWidget(self.pushButton_color_selector, 1, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout_3.addWidget(self.label_2, 2, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) + self.comboBox_image_comparison_function = QtWidgets.QComboBox(self.groupBox) + self.comboBox_image_comparison_function.setObjectName("comboBox_image_comparison_function") + self.gridLayout_3.addWidget(self.comboBox_image_comparison_function, 2, 1, 1, 1) + self.pushButton_clear_feature_grid_list = QtWidgets.QPushButton(self.groupBox) + self.pushButton_clear_feature_grid_list.setObjectName("pushButton_clear_feature_grid_list") + self.gridLayout_3.addWidget(self.pushButton_clear_feature_grid_list, 3, 0, 1, 2) + self.gridLayout_2.addWidget(self.groupBox, 0, 0, 1, 2) + self.gridLayout.addWidget(self.widget_2, 0, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(FeatureGridDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 1, 1, 1) + + self.retranslateUi(FeatureGridDialog) + self.buttonBox.accepted.connect(FeatureGridDialog.accept) + self.buttonBox.rejected.connect(FeatureGridDialog.reject) + QtCore.QMetaObject.connectSlotsByName(FeatureGridDialog) + + def retranslateUi(self, FeatureGridDialog): + _translate = QtCore.QCoreApplication.translate + FeatureGridDialog.setWindowTitle(_translate("FeatureGridDialog", "Image Dialog")) + self.groupBox_settings_feature_detection.setTitle(_translate("FeatureGridDialog", "Auto Detect Grid Points")) + self.label_47.setText(_translate("FeatureGridDialog", "Overlap")) + self.label_42.setText(_translate("FeatureGridDialog", "Minimum Sigma")) + self.label_43.setText(_translate("FeatureGridDialog", "Feature Auto Detect Function")) + self.spinBox_feature_auto_detect_num_sigma.setToolTip(_translate("FeatureGridDialog", "The number of intermediate values of standard deviations to consider\n" +" ")) + self.doubleSpinBox_feature_auto_detect_overlap.setToolTip(_translate("FeatureGridDialog", "A value between 0 and 1. If the area of two blobs overlaps by a\n" +" fraction greater than `threshold`, the smaller blob is eliminated.\n" +" ")) + self.label_45.setText(_translate("FeatureGridDialog", "Num Sigma")) + self.label_44.setText(_translate("FeatureGridDialog", "Maximum Sigma")) + self.label_46.setText(_translate("FeatureGridDialog", "Threshold")) + self.doubleSpinBox_feature_auto_detect_min_sigma.setToolTip(_translate("FeatureGridDialog", "

The minimum standard\n" +" deviation for Gaussian kernel. Keep this low to

detect\n" +" smaller blobs.

\n" +" ")) + self.doubleSpinBox_feature_auto_detect_threshold.setToolTip(_translate("FeatureGridDialog", "The absolute lower bound for scale space maxima. Local maxima smaller\n" +" than thresh are ignored. Reduce this to detect blobs with less\n" +" intensities.\n" +" ")) + self.label_59.setText(_translate("FeatureGridDialog", "Detect Maxima")) + self.doubleSpinBox_feature_auto_detect_max_sigma.setToolTip(_translate("FeatureGridDialog", "The maximum standard deviation for Gaussian kernel. Keep this high to\n" +" detect larger blobs\n" +" ")) + self.pushButton_detect_grid_points.setText(_translate("FeatureGridDialog", "Detect")) + self.groupBox.setTitle(_translate("FeatureGridDialog", "Grid Settings")) + self.pushButton_color_selector.setText(_translate("FeatureGridDialog", "Select Color")) + self.label_2.setText(_translate("FeatureGridDialog", "Function")) + self.label.setText(_translate("FeatureGridDialog", "Point Size")) + self.pushButton_clear_feature_grid_list.setText(_translate("FeatureGridDialog", "Clear All")) diff --git a/view/ui/ui_dialog_feature_rag.py b/view/ui/ui_dialog_feature_rag.py new file mode 100644 index 0000000..a8d6ce0 --- /dev/null +++ b/view/ui/ui_dialog_feature_rag.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFeatureRag.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(797, 586) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) + Dialog.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 1, 1, 2) + self.widget = QtWidgets.QWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) + self.widget.setObjectName("widget") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_3.setObjectName("gridLayout_3") + self.groupBox_feature = QtWidgets.QGroupBox(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_feature.sizePolicy().hasHeightForWidth()) + self.groupBox_feature.setSizePolicy(sizePolicy) + self.groupBox_feature.setObjectName("groupBox_feature") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_feature) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label_2 = QtWidgets.QLabel(self.groupBox_feature) + self.label_2.setObjectName("label_2") + self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 1) + self.listWidget_feature_class = QtWidgets.QListWidget(self.groupBox_feature) + self.listWidget_feature_class.setObjectName("listWidget_feature_class") + self.gridLayout_4.addWidget(self.listWidget_feature_class, 1, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(self.groupBox_feature) + self.label_3.setObjectName("label_3") + self.gridLayout_4.addWidget(self.label_3, 3, 0, 1, 1) + self.listWidget_feature = QtWidgets.QListWidget(self.groupBox_feature) + self.listWidget_feature.setObjectName("listWidget_feature") + self.gridLayout_4.addWidget(self.listWidget_feature, 4, 0, 1, 1) + self.widget_3 = QtWidgets.QWidget(self.groupBox_feature) + self.widget_3.setObjectName("widget_3") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pushButton_feature_list_check_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_check_all.setObjectName("pushButton_feature_list_check_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_check_all) + self.pushButton_feature_list_invert = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_invert.setObjectName("pushButton_feature_list_invert") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_invert) + self.pushButton_feature_list_uncheck_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_uncheck_all.setObjectName("pushButton_feature_list_uncheck_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_uncheck_all) + self.gridLayout_4.addWidget(self.widget_3, 5, 0, 1, 1) + self.widget_4 = QtWidgets.QWidget(self.groupBox_feature) + self.widget_4.setObjectName("widget_4") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_4) + self.horizontalLayout.setObjectName("horizontalLayout") + self.pushButton_feature_class_check_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_check_all.setObjectName("pushButton_feature_class_check_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_check_all) + self.pushButton_feature_class_invert = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_invert.setObjectName("pushButton_feature_class_invert") + self.horizontalLayout.addWidget(self.pushButton_feature_class_invert) + self.pushButton_feature_class_uncheck_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_uncheck_all.setObjectName("pushButton_feature_class_uncheck_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_uncheck_all) + self.gridLayout_4.addWidget(self.widget_4, 2, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_feature, 1, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.spinBox_image_number = QtWidgets.QSpinBox(self.groupBox) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_2.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.horizontalSlider_image_number = QtWidgets.QSlider(self.groupBox) + self.horizontalSlider_image_number.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_image_number.setObjectName("horizontalSlider_image_number") + self.gridLayout_2.addWidget(self.horizontalSlider_image_number, 1, 0, 1, 2) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 3, 0, 1, 1) + self.pushButton_force_recreate_rags = QtWidgets.QPushButton(self.groupBox) + self.pushButton_force_recreate_rags.setObjectName("pushButton_force_recreate_rags") + self.gridLayout_2.addWidget(self.pushButton_force_recreate_rags, 2, 0, 1, 2) + self.gridLayout_3.addWidget(self.groupBox, 0, 0, 1, 1) + self.groupBox_2 = QtWidgets.QGroupBox(self.widget) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_5.setObjectName("gridLayout_5") + self.textBrowser_analyse = QtWidgets.QTextBrowser(self.groupBox_2) + self.textBrowser_analyse.setObjectName("textBrowser_analyse") + self.gridLayout_5.addWidget(self.textBrowser_analyse, 0, 0, 1, 1) + self.pushButton_export = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_export.setObjectName("pushButton_export") + self.gridLayout_5.addWidget(self.pushButton_export, 1, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_2, 2, 0, 1, 1) + self.gridLayout.addWidget(self.widget, 0, 2, 1, 1) + self.widget_2 = QtWidgets.QWidget(Dialog) + self.widget_2.setObjectName("widget_2") + self.verticalLayout = QtWidgets.QVBoxLayout(self.widget_2) + self.verticalLayout.setObjectName("verticalLayout") + self.widget_render_window = QtWidgets.QWidget(self.widget_2) + self.widget_render_window.setObjectName("widget_render_window") + self.verticalLayout.addWidget(self.widget_render_window) + self.widget_neighbor_graph = QtWidgets.QWidget(self.widget_2) + self.widget_neighbor_graph.setObjectName("widget_neighbor_graph") + self.verticalLayout.addWidget(self.widget_neighbor_graph) + self.gridLayout.addWidget(self.widget_2, 0, 0, 1, 2) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + self.spinBox_image_number.valueChanged['int'].connect(self.horizontalSlider_image_number.setValue) + self.horizontalSlider_image_number.valueChanged['int'].connect(self.spinBox_image_number.setValue) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox_feature.setTitle(_translate("Dialog", "Feature")) + self.label_2.setText(_translate("Dialog", "Feature Class")) + self.label_3.setText(_translate("Dialog", "Feature")) + self.pushButton_feature_list_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_list_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_list_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.pushButton_feature_class_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_class_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_class_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.groupBox.setTitle(_translate("Dialog", "Settings")) + self.label.setText(_translate("Dialog", "Image Number")) + self.pushButton_force_recreate_rags.setText(_translate("Dialog", "Force Recreate Rags")) + self.groupBox_2.setTitle(_translate("Dialog", "Analyse")) + self.pushButton_export.setText(_translate("Dialog", "Export")) diff --git a/view/ui/ui_dialog_filter_1d.py b/view/ui/ui_dialog_filter_1d.py new file mode 100644 index 0000000..f8300d7 --- /dev/null +++ b/view/ui/ui_dialog_filter_1d.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFilter_1D.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog_filter(object): + def setupUi(self, Dialog_filter): + Dialog_filter.setObjectName("Dialog_filter") + Dialog_filter.resize(985, 677) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog_filter) + self.verticalLayout.setObjectName("verticalLayout") + self.widget_5 = QtWidgets.QWidget(Dialog_filter) + self.widget_5.setObjectName("widget_5") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_5) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.widget_6 = QtWidgets.QWidget(self.widget_5) + self.widget_6.setObjectName("widget_6") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.widget_6) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.label = QtWidgets.QLabel(self.widget_6) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.verticalLayout_5.addWidget(self.label) + self.widget_vtk_filter_preview = QtWidgets.QWidget(self.widget_6) + self.widget_vtk_filter_preview.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_preview.setObjectName("widget_vtk_filter_preview") + self.verticalLayout_5.addWidget(self.widget_vtk_filter_preview) + self.horizontalLayout_2.addWidget(self.widget_6) + self.widget_8 = QtWidgets.QWidget(self.widget_5) + self.widget_8.setObjectName("widget_8") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.widget_8) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.label_3 = QtWidgets.QLabel(self.widget_8) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) + self.label_3.setSizePolicy(sizePolicy) + self.label_3.setObjectName("label_3") + self.verticalLayout_7.addWidget(self.label_3) + self.widget_vtk_filter_1d_signal = QtWidgets.QWidget(self.widget_8) + self.widget_vtk_filter_1d_signal.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_1d_signal.setObjectName("widget_vtk_filter_1d_signal") + self.verticalLayout_7.addWidget(self.widget_vtk_filter_1d_signal) + self.checkBox_1d_signal_autoscale = QtWidgets.QCheckBox(self.widget_8) + self.checkBox_1d_signal_autoscale.setObjectName("checkBox_1d_signal_autoscale") + self.verticalLayout_7.addWidget(self.checkBox_1d_signal_autoscale) + self.horizontalLayout_2.addWidget(self.widget_8) + self.widget_7 = QtWidgets.QWidget(self.widget_5) + self.widget_7.setObjectName("widget_7") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.widget_7) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.label_2 = QtWidgets.QLabel(self.widget_7) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName("label_2") + self.verticalLayout_6.addWidget(self.label_2) + self.widget_vtk_filter_fft = QtWidgets.QWidget(self.widget_7) + self.widget_vtk_filter_fft.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_fft.setObjectName("widget_vtk_filter_fft") + self.verticalLayout_6.addWidget(self.widget_vtk_filter_fft) + self.checkBox_1d_fourier_auto_scale = QtWidgets.QCheckBox(self.widget_7) + self.checkBox_1d_fourier_auto_scale.setObjectName("checkBox_1d_fourier_auto_scale") + self.verticalLayout_6.addWidget(self.checkBox_1d_fourier_auto_scale) + self.horizontalLayout_2.addWidget(self.widget_7) + self.verticalLayout.addWidget(self.widget_5) + self.widget = QtWidgets.QWidget(Dialog_filter) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + self.widget_4 = QtWidgets.QWidget(self.widget) + self.widget_4.setObjectName("widget_4") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.widget_4) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.listWidget_filter_list = QtWidgets.QListWidget(self.widget_4) + self.listWidget_filter_list.setDragEnabled(True) + self.listWidget_filter_list.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + self.listWidget_filter_list.setAlternatingRowColors(True) + self.listWidget_filter_list.setObjectName("listWidget_filter_list") + self.verticalLayout_4.addWidget(self.listWidget_filter_list) + self.pushButton_apply_filter_test = QtWidgets.QPushButton(self.widget_4) + self.pushButton_apply_filter_test.setObjectName("pushButton_apply_filter_test") + self.verticalLayout_4.addWidget(self.pushButton_apply_filter_test) + self.horizontalLayout.addWidget(self.widget_4) + self.widget_3 = QtWidgets.QWidget(self.widget) + self.widget_3.setObjectName("widget_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget_3) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.pushButton_add_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_add_filter.setObjectName("pushButton_add_filter") + self.verticalLayout_3.addWidget(self.pushButton_add_filter) + self.pushButton_delete_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_delete_filter.setObjectName("pushButton_delete_filter") + self.verticalLayout_3.addWidget(self.pushButton_delete_filter) + self.horizontalLayout.addWidget(self.widget_3) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget_2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.comboBox_filter_choser = QtWidgets.QComboBox(self.widget_2) + self.comboBox_filter_choser.setObjectName("comboBox_filter_choser") + self.verticalLayout_2.addWidget(self.comboBox_filter_choser) + self.gridLayout_filter_inputs = QtWidgets.QGridLayout() + self.gridLayout_filter_inputs.setObjectName("gridLayout_filter_inputs") + self.verticalLayout_2.addLayout(self.gridLayout_filter_inputs) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem) + self.horizontalLayout.addWidget(self.widget_2) + self.verticalLayout.addWidget(self.widget) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog_filter) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog_filter) + self.buttonBox.accepted.connect(Dialog_filter.accept) + self.buttonBox.rejected.connect(Dialog_filter.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog_filter) + + def retranslateUi(self, Dialog_filter): + _translate = QtCore.QCoreApplication.translate + Dialog_filter.setWindowTitle(_translate("Dialog_filter", "Filter Dialog")) + self.label.setText(_translate("Dialog_filter", "Real Image")) + self.label_3.setText(_translate("Dialog_filter", "1D - Signal")) + self.checkBox_1d_signal_autoscale.setText(_translate("Dialog_filter", "Autoscale")) + self.label_2.setText(_translate("Dialog_filter", "1D - Fourier Transformed (log)")) + self.checkBox_1d_fourier_auto_scale.setText(_translate("Dialog_filter", "Autosacle")) + self.pushButton_apply_filter_test.setText(_translate("Dialog_filter", "Apply Filters")) + self.pushButton_add_filter.setText(_translate("Dialog_filter", "Add")) + self.pushButton_delete_filter.setText(_translate("Dialog_filter", "Delete ")) diff --git a/view/ui/ui_dialog_filter_1d_full_signal.py b/view/ui/ui_dialog_filter_1d_full_signal.py new file mode 100644 index 0000000..46f3be9 --- /dev/null +++ b/view/ui/ui_dialog_filter_1d_full_signal.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFilter_1D_Full_Signal.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog_filter(object): + def setupUi(self, Dialog_filter): + Dialog_filter.setObjectName("Dialog_filter") + Dialog_filter.resize(985, 677) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog_filter) + self.verticalLayout.setObjectName("verticalLayout") + self.widget_5 = QtWidgets.QWidget(Dialog_filter) + self.widget_5.setObjectName("widget_5") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_5) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.widget_6 = QtWidgets.QWidget(self.widget_5) + self.widget_6.setObjectName("widget_6") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.widget_6) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.label = QtWidgets.QLabel(self.widget_6) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.verticalLayout_5.addWidget(self.label) + self.widget_vtk_filter_preview = QtWidgets.QWidget(self.widget_6) + self.widget_vtk_filter_preview.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_preview.setObjectName("widget_vtk_filter_preview") + self.verticalLayout_5.addWidget(self.widget_vtk_filter_preview) + self.horizontalLayout_2.addWidget(self.widget_6) + self.widget_8 = QtWidgets.QWidget(self.widget_5) + self.widget_8.setObjectName("widget_8") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.widget_8) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.label_3 = QtWidgets.QLabel(self.widget_8) + self.label_3.setObjectName("label_3") + self.verticalLayout_7.addWidget(self.label_3) + self.widget_vtk_filter_1d_signal = QtWidgets.QWidget(self.widget_8) + self.widget_vtk_filter_1d_signal.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_1d_signal.setObjectName("widget_vtk_filter_1d_signal") + self.verticalLayout_7.addWidget(self.widget_vtk_filter_1d_signal) + self.checkBox_1d_signal_autoscale = QtWidgets.QCheckBox(self.widget_8) + self.checkBox_1d_signal_autoscale.setObjectName("checkBox_1d_signal_autoscale") + self.verticalLayout_7.addWidget(self.checkBox_1d_signal_autoscale) + self.horizontalLayout_2.addWidget(self.widget_8) + self.widget_7 = QtWidgets.QWidget(self.widget_5) + self.widget_7.setObjectName("widget_7") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.widget_7) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.label_2 = QtWidgets.QLabel(self.widget_7) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName("label_2") + self.verticalLayout_6.addWidget(self.label_2) + self.widget_vtk_filter_fft = QtWidgets.QWidget(self.widget_7) + self.widget_vtk_filter_fft.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_fft.setObjectName("widget_vtk_filter_fft") + self.verticalLayout_6.addWidget(self.widget_vtk_filter_fft) + self.checkBox_1d_fourier_auto_scale = QtWidgets.QCheckBox(self.widget_7) + self.checkBox_1d_fourier_auto_scale.setObjectName("checkBox_1d_fourier_auto_scale") + self.verticalLayout_6.addWidget(self.checkBox_1d_fourier_auto_scale) + self.horizontalLayout_2.addWidget(self.widget_7) + self.verticalLayout.addWidget(self.widget_5) + self.widget = QtWidgets.QWidget(Dialog_filter) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + self.widget_4 = QtWidgets.QWidget(self.widget) + self.widget_4.setObjectName("widget_4") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.widget_4) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.listWidget_filter_list = QtWidgets.QListWidget(self.widget_4) + self.listWidget_filter_list.setDragEnabled(True) + self.listWidget_filter_list.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + self.listWidget_filter_list.setAlternatingRowColors(True) + self.listWidget_filter_list.setObjectName("listWidget_filter_list") + self.verticalLayout_4.addWidget(self.listWidget_filter_list) + self.pushButton_apply_filter_test = QtWidgets.QPushButton(self.widget_4) + self.pushButton_apply_filter_test.setObjectName("pushButton_apply_filter_test") + self.verticalLayout_4.addWidget(self.pushButton_apply_filter_test) + self.horizontalLayout.addWidget(self.widget_4) + self.widget_3 = QtWidgets.QWidget(self.widget) + self.widget_3.setObjectName("widget_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget_3) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.pushButton_add_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_add_filter.setObjectName("pushButton_add_filter") + self.verticalLayout_3.addWidget(self.pushButton_add_filter) + self.pushButton_delete_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_delete_filter.setObjectName("pushButton_delete_filter") + self.verticalLayout_3.addWidget(self.pushButton_delete_filter) + self.horizontalLayout.addWidget(self.widget_3) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget_2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.comboBox_filter_choser = QtWidgets.QComboBox(self.widget_2) + self.comboBox_filter_choser.setObjectName("comboBox_filter_choser") + self.verticalLayout_2.addWidget(self.comboBox_filter_choser) + self.gridLayout_filter_inputs = QtWidgets.QGridLayout() + self.gridLayout_filter_inputs.setObjectName("gridLayout_filter_inputs") + self.verticalLayout_2.addLayout(self.gridLayout_filter_inputs) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem) + self.horizontalLayout.addWidget(self.widget_2) + self.verticalLayout.addWidget(self.widget) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog_filter) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog_filter) + self.buttonBox.accepted.connect(Dialog_filter.accept) + self.buttonBox.rejected.connect(Dialog_filter.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog_filter) + + def retranslateUi(self, Dialog_filter): + _translate = QtCore.QCoreApplication.translate + Dialog_filter.setWindowTitle(_translate("Dialog_filter", "Filter Full Signal Dialog")) + self.label.setText(_translate("Dialog_filter", "Real Image")) + self.label_3.setText(_translate("Dialog_filter", "1D - Signal")) + self.checkBox_1d_signal_autoscale.setText(_translate("Dialog_filter", "Autoscale")) + self.label_2.setText(_translate("Dialog_filter", "1D - Fourier Transformed (log)")) + self.checkBox_1d_fourier_auto_scale.setText(_translate("Dialog_filter", "Autosacle")) + self.pushButton_apply_filter_test.setText(_translate("Dialog_filter", "Apply Filters")) + self.pushButton_add_filter.setText(_translate("Dialog_filter", "Add")) + self.pushButton_delete_filter.setText(_translate("Dialog_filter", "Delete")) diff --git a/view/ui/ui_dialog_filter_2d.py b/view/ui/ui_dialog_filter_2d.py new file mode 100644 index 0000000..c632081 --- /dev/null +++ b/view/ui/ui_dialog_filter_2d.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFilter_2D.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog_filter(object): + def setupUi(self, Dialog_filter): + Dialog_filter.setObjectName("Dialog_filter") + Dialog_filter.resize(827, 592) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog_filter) + self.verticalLayout.setObjectName("verticalLayout") + self.widget_5 = QtWidgets.QWidget(Dialog_filter) + self.widget_5.setObjectName("widget_5") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_5) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.widget_6 = QtWidgets.QWidget(self.widget_5) + self.widget_6.setObjectName("widget_6") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.widget_6) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.label = QtWidgets.QLabel(self.widget_6) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.verticalLayout_5.addWidget(self.label) + self.widget_vtk_filter_preview = QtWidgets.QWidget(self.widget_6) + self.widget_vtk_filter_preview.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_preview.setObjectName("widget_vtk_filter_preview") + self.verticalLayout_5.addWidget(self.widget_vtk_filter_preview) + self.horizontalLayout_2.addWidget(self.widget_6) + self.widget_7 = QtWidgets.QWidget(self.widget_5) + self.widget_7.setObjectName("widget_7") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.widget_7) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.label_2 = QtWidgets.QLabel(self.widget_7) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName("label_2") + self.verticalLayout_6.addWidget(self.label_2) + self.widget_vtk_filter_fft = QtWidgets.QWidget(self.widget_7) + self.widget_vtk_filter_fft.setMinimumSize(QtCore.QSize(200, 200)) + self.widget_vtk_filter_fft.setObjectName("widget_vtk_filter_fft") + self.verticalLayout_6.addWidget(self.widget_vtk_filter_fft) + self.horizontalLayout_2.addWidget(self.widget_7) + self.verticalLayout.addWidget(self.widget_5) + self.widget = QtWidgets.QWidget(Dialog_filter) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.widget_4 = QtWidgets.QWidget(self.widget) + self.widget_4.setObjectName("widget_4") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.widget_4) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.listWidget_filter_list = QtWidgets.QListWidget(self.widget_4) + self.listWidget_filter_list.setDragEnabled(True) + self.listWidget_filter_list.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + self.listWidget_filter_list.setAlternatingRowColors(True) + self.listWidget_filter_list.setObjectName("listWidget_filter_list") + self.verticalLayout_4.addWidget(self.listWidget_filter_list) + self.pushButton_apply_filter_test = QtWidgets.QPushButton(self.widget_4) + self.pushButton_apply_filter_test.setObjectName("pushButton_apply_filter_test") + self.verticalLayout_4.addWidget(self.pushButton_apply_filter_test) + self.horizontalLayout.addWidget(self.widget_4) + self.widget_3 = QtWidgets.QWidget(self.widget) + self.widget_3.setObjectName("widget_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget_3) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.pushButton_add_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_add_filter.setObjectName("pushButton_add_filter") + self.verticalLayout_3.addWidget(self.pushButton_add_filter) + self.pushButton_delete_filter = QtWidgets.QPushButton(self.widget_3) + self.pushButton_delete_filter.setObjectName("pushButton_delete_filter") + self.verticalLayout_3.addWidget(self.pushButton_delete_filter) + self.horizontalLayout.addWidget(self.widget_3) + self.widget_2 = QtWidgets.QWidget(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget_2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.groupBox = QtWidgets.QGroupBox(self.widget_2) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.comboBox_filter_choser = QtWidgets.QComboBox(self.groupBox) + self.comboBox_filter_choser.setObjectName("comboBox_filter_choser") + self.gridLayout_2.addWidget(self.comboBox_filter_choser, 0, 0, 1, 1) + self.verticalLayout_2.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.comboBox_saved_filter = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_saved_filter.setObjectName("comboBox_saved_filter") + self.gridLayout_3.addWidget(self.comboBox_saved_filter, 0, 0, 1, 2) + self.lineEdit_saved_filter_name = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_saved_filter_name.setObjectName("lineEdit_saved_filter_name") + self.gridLayout_3.addWidget(self.lineEdit_saved_filter_name, 1, 0, 1, 1) + self.pushButton_saved_filter = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_saved_filter.setObjectName("pushButton_saved_filter") + self.gridLayout_3.addWidget(self.pushButton_saved_filter, 1, 1, 1, 1) + self.verticalLayout_2.addWidget(self.groupBox_2) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.gridLayout_filter_inputs = QtWidgets.QGridLayout() + self.gridLayout_filter_inputs.setObjectName("gridLayout_filter_inputs") + self.gridLayout_4.addLayout(self.gridLayout_filter_inputs, 0, 0, 1, 1) + self.verticalLayout_2.addWidget(self.groupBox_3) + self.widget_8 = QtWidgets.QWidget(self.widget_2) + self.widget_8.setObjectName("widget_8") + self.gridLayout = QtWidgets.QGridLayout(self.widget_8) + self.gridLayout.setObjectName("gridLayout") + self.verticalLayout_2.addWidget(self.widget_8) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem1) + self.horizontalLayout.addWidget(self.widget_2) + self.verticalLayout.addWidget(self.widget) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog_filter) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog_filter) + self.buttonBox.accepted.connect(Dialog_filter.accept) + self.buttonBox.rejected.connect(Dialog_filter.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog_filter) + + def retranslateUi(self, Dialog_filter): + _translate = QtCore.QCoreApplication.translate + Dialog_filter.setWindowTitle(_translate("Dialog_filter", "Filter Dialog")) + self.label.setText(_translate("Dialog_filter", "Real Image")) + self.label_2.setText(_translate("Dialog_filter", "Fourier Transformed (log)")) + self.pushButton_apply_filter_test.setText(_translate("Dialog_filter", "Apply Filters")) + self.pushButton_add_filter.setText(_translate("Dialog_filter", "Add")) + self.pushButton_delete_filter.setText(_translate("Dialog_filter", "Delete ")) + self.groupBox.setTitle(_translate("Dialog_filter", "Filter Class")) + self.groupBox_2.setTitle(_translate("Dialog_filter", "Saved Filters")) + self.pushButton_saved_filter.setText(_translate("Dialog_filter", "Save")) + self.groupBox_3.setTitle(_translate("Dialog_filter", "Filter Inputs")) diff --git a/view/ui/ui_dialog_import_features.py b/view/ui/ui_dialog_import_features.py new file mode 100644 index 0000000..f2c40b0 --- /dev/null +++ b/view/ui/ui_dialog_import_features.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogImportFeatures.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(287, 275) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.label_2 = QtWidgets.QLabel(Dialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(Dialog) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.lineEdit_file_path = QtWidgets.QLineEdit(Dialog) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.gridLayout.addWidget(self.lineEdit_file_path, 0, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(Dialog) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) + self.pushButton_browse_file_system = QtWidgets.QPushButton(Dialog) + self.pushButton_browse_file_system.setObjectName("pushButton_browse_file_system") + self.gridLayout.addWidget(self.pushButton_browse_file_system, 0, 2, 1, 1) + self.doubleSpinBox_x_ratio = QtWidgets.QDoubleSpinBox(Dialog) + self.doubleSpinBox_x_ratio.setDecimals(4) + self.doubleSpinBox_x_ratio.setMaximum(10000.0) + self.doubleSpinBox_x_ratio.setProperty("value", 1.0) + self.doubleSpinBox_x_ratio.setObjectName("doubleSpinBox_x_ratio") + self.gridLayout.addWidget(self.doubleSpinBox_x_ratio, 1, 1, 1, 2) + self.doubleSpinBox_y_ratio = QtWidgets.QDoubleSpinBox(Dialog) + self.doubleSpinBox_y_ratio.setDecimals(4) + self.doubleSpinBox_y_ratio.setMaximum(10000.0) + self.doubleSpinBox_y_ratio.setProperty("value", 1.0) + self.doubleSpinBox_y_ratio.setObjectName("doubleSpinBox_y_ratio") + self.gridLayout.addWidget(self.doubleSpinBox_y_ratio, 2, 1, 1, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 2) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_2.setText(_translate("Dialog", "X Ratio")) + self.label.setText(_translate("Dialog", "File Path")) + self.label_3.setText(_translate("Dialog", "Y Ratio")) + self.pushButton_browse_file_system.setText(_translate("Dialog", "Browse")) diff --git a/view/ui/ui_dialog_import_gwy.py b/view/ui/ui_dialog_import_gwy.py new file mode 100644 index 0000000..ae549ff --- /dev/null +++ b/view/ui/ui_dialog_import_gwy.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogImportGWY.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogImportGWY(object): + def setupUi(self, DialogImportGWY): + DialogImportGWY.setObjectName("DialogImportGWY") + DialogImportGWY.resize(574, 300) + self.verticalLayout = QtWidgets.QVBoxLayout(DialogImportGWY) + self.verticalLayout.setObjectName("verticalLayout") + self.widget = QtWidgets.QWidget(DialogImportGWY) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.comboBox_gwy_channel = QtWidgets.QComboBox(self.widget_2) + self.comboBox_gwy_channel.setEnabled(False) + self.comboBox_gwy_channel.setObjectName("comboBox_gwy_channel") + self.horizontalLayout.addWidget(self.comboBox_gwy_channel) + self.gridLayout_2.addWidget(self.widget_2, 2, 2, 1, 2) + self.pushButton_browse_filesystem = QtWidgets.QPushButton(self.widget) + self.pushButton_browse_filesystem.setObjectName("pushButton_browse_filesystem") + self.gridLayout_2.addWidget(self.pushButton_browse_filesystem, 1, 3, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 1, 1, 1, 1) + self.lineEdit_file_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.gridLayout_2.addWidget(self.lineEdit_file_path, 1, 2, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 2, 1, 1, 1) + self.verticalLayout.addWidget(self.widget) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(DialogImportGWY) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(DialogImportGWY) + self.buttonBox.accepted.connect(DialogImportGWY.accept) + self.buttonBox.rejected.connect(DialogImportGWY.reject) + QtCore.QMetaObject.connectSlotsByName(DialogImportGWY) + + def retranslateUi(self, DialogImportGWY): + _translate = QtCore.QCoreApplication.translate + DialogImportGWY.setWindowTitle(_translate("DialogImportGWY", "Import gwy file")) + self.pushButton_browse_filesystem.setText(_translate("DialogImportGWY", "Browse Filesystem")) + self.label.setText(_translate("DialogImportGWY", "File Path:")) + self.label_2.setText(_translate("DialogImportGWY", "Channel:")) diff --git a/view/ui/ui_dialog_import_h5.py b/view/ui/ui_dialog_import_h5.py new file mode 100644 index 0000000..7831a2c --- /dev/null +++ b/view/ui/ui_dialog_import_h5.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogImportH5.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogImportH5(object): + def setupUi(self, DialogImportH5): + DialogImportH5.setObjectName("DialogImportH5") + DialogImportH5.resize(636, 471) + self.verticalLayout = QtWidgets.QVBoxLayout(DialogImportH5) + self.verticalLayout.setObjectName("verticalLayout") + self.widget = QtWidgets.QWidget(DialogImportH5) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.checkBox_scan_direction_out = QtWidgets.QCheckBox(self.widget) + self.checkBox_scan_direction_out.setObjectName("checkBox_scan_direction_out") + self.gridLayout_2.addWidget(self.checkBox_scan_direction_out, 6, 2, 1, 1) + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 9, 1, 1, 1) + self.spinBox_number_of_bins = QtWidgets.QSpinBox(self.widget) + self.spinBox_number_of_bins.setMaximum(100000) + self.spinBox_number_of_bins.setProperty("value", 100) + self.spinBox_number_of_bins.setObjectName("spinBox_number_of_bins") + self.gridLayout_2.addWidget(self.spinBox_number_of_bins, 3, 2, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 1, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.widget) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 12, 1, 1, 1) + self.radioButton_full_scan = QtWidgets.QRadioButton(self.widget) + self.radioButton_full_scan.setObjectName("radioButton_full_scan") + self.gridLayout_2.addWidget(self.radioButton_full_scan, 10, 2, 1, 1) + self.pushButton_browse_filesystem = QtWidgets.QPushButton(self.widget) + self.pushButton_browse_filesystem.setObjectName("pushButton_browse_filesystem") + self.gridLayout_2.addWidget(self.pushButton_browse_filesystem, 1, 3, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.spinBox_import_image_start = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_import_image_start.setMaximum(1000000) + self.spinBox_import_image_start.setObjectName("spinBox_import_image_start") + self.horizontalLayout.addWidget(self.spinBox_import_image_start) + self.label_6 = QtWidgets.QLabel(self.widget_2) + self.label_6.setAlignment(QtCore.Qt.AlignCenter) + self.label_6.setObjectName("label_6") + self.horizontalLayout.addWidget(self.label_6) + self.spinBox_import_image_end = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_import_image_end.setMaximum(1000000) + self.spinBox_import_image_end.setObjectName("spinBox_import_image_end") + self.horizontalLayout.addWidget(self.spinBox_import_image_end) + self.gridLayout_2.addWidget(self.widget_2, 9, 2, 1, 2) + self.label_8 = QtWidgets.QLabel(self.widget) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.checkBox_use_ideal_xy_values = QtWidgets.QCheckBox(self.widget) + self.checkBox_use_ideal_xy_values.setText("") + self.checkBox_use_ideal_xy_values.setObjectName("checkBox_use_ideal_xy_values") + self.gridLayout_2.addWidget(self.checkBox_use_ideal_xy_values, 14, 2, 1, 1) + self.label_10 = QtWidgets.QLabel(self.widget) + self.label_10.setObjectName("label_10") + self.gridLayout_2.addWidget(self.label_10, 13, 1, 1, 1) + self.checkBox_scan_direction_combined = QtWidgets.QCheckBox(self.widget) + self.checkBox_scan_direction_combined.setChecked(False) + self.checkBox_scan_direction_combined.setObjectName("checkBox_scan_direction_combined") + self.gridLayout_2.addWidget(self.checkBox_scan_direction_combined, 5, 2, 1, 2) + self.checkBox_use_first_image_xy_values = QtWidgets.QCheckBox(self.widget) + self.checkBox_use_first_image_xy_values.setText("") + self.checkBox_use_first_image_xy_values.setObjectName("checkBox_use_first_image_xy_values") + self.gridLayout_2.addWidget(self.checkBox_use_first_image_xy_values, 13, 2, 1, 1) + self.label_3 = QtWidgets.QLabel(self.widget) + self.label_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 10, 1, 1, 1) + self.label_11 = QtWidgets.QLabel(self.widget) + self.label_11.setObjectName("label_11") + self.gridLayout_2.addWidget(self.label_11, 14, 1, 1, 1) + self.label_9 = QtWidgets.QLabel(self.widget) + self.label_9.setObjectName("label_9") + self.gridLayout_2.addWidget(self.label_9, 5, 1, 1, 1) + self.checkBox_scan_direction_seperated = QtWidgets.QCheckBox(self.widget) + self.checkBox_scan_direction_seperated.setObjectName("checkBox_scan_direction_seperated") + self.gridLayout_2.addWidget(self.checkBox_scan_direction_seperated, 8, 2, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 3, 1, 1, 1) + self.radioButton_square_scan = QtWidgets.QRadioButton(self.widget) + self.radioButton_square_scan.setChecked(True) + self.radioButton_square_scan.setObjectName("radioButton_square_scan") + self.gridLayout_2.addWidget(self.radioButton_square_scan, 10, 3, 1, 1) + self.checkBox_cut_zoom = QtWidgets.QCheckBox(self.widget) + self.checkBox_cut_zoom.setText("") + self.checkBox_cut_zoom.setChecked(True) + self.checkBox_cut_zoom.setObjectName("checkBox_cut_zoom") + self.gridLayout_2.addWidget(self.checkBox_cut_zoom, 11, 2, 1, 1) + self.label_4 = QtWidgets.QLabel(self.widget) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 11, 1, 1, 1) + self.lineEdit_file_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.gridLayout_2.addWidget(self.lineEdit_file_path, 1, 2, 1, 1) + self.checkBox_scan_direction_in = QtWidgets.QCheckBox(self.widget) + self.checkBox_scan_direction_in.setObjectName("checkBox_scan_direction_in") + self.gridLayout_2.addWidget(self.checkBox_scan_direction_in, 7, 2, 1, 1) + self.comboBox_to_grid_method = QtWidgets.QComboBox(self.widget) + self.comboBox_to_grid_method.setObjectName("comboBox_to_grid_method") + self.gridLayout_2.addWidget(self.comboBox_to_grid_method, 2, 2, 1, 2) + self.checkBox_remove_offset = QtWidgets.QCheckBox(self.widget) + self.checkBox_remove_offset.setText("") + self.checkBox_remove_offset.setObjectName("checkBox_remove_offset") + self.gridLayout_2.addWidget(self.checkBox_remove_offset, 12, 2, 1, 1) + self.label_12 = QtWidgets.QLabel(self.widget) + self.label_12.setObjectName("label_12") + self.gridLayout_2.addWidget(self.label_12, 4, 1, 1, 1) + self.checkBox_use_4th_as_z = QtWidgets.QCheckBox(self.widget) + self.checkBox_use_4th_as_z.setText("") + self.checkBox_use_4th_as_z.setObjectName("checkBox_use_4th_as_z") + self.gridLayout_2.addWidget(self.checkBox_use_4th_as_z, 4, 2, 1, 1) + self.verticalLayout.addWidget(self.widget) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(DialogImportH5) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(DialogImportH5) + self.buttonBox.accepted.connect(DialogImportH5.accept) + self.buttonBox.rejected.connect(DialogImportH5.reject) + QtCore.QMetaObject.connectSlotsByName(DialogImportH5) + + def retranslateUi(self, DialogImportH5): + _translate = QtCore.QCoreApplication.translate + DialogImportH5.setWindowTitle(_translate("DialogImportH5", "Import h5 file")) + self.checkBox_scan_direction_out.setText(_translate("DialogImportH5", "Out")) + self.label_5.setText(_translate("DialogImportH5", "Import images from")) + self.label.setText(_translate("DialogImportH5", "File Path:")) + self.label_7.setToolTip(_translate("DialogImportH5", "

Removes the offset of the z-values (value = value - min(values))

")) + self.label_7.setText(_translate("DialogImportH5", "Remove Offset")) + self.radioButton_full_scan.setToolTip(_translate("DialogImportH5", "

If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters

")) + self.radioButton_full_scan.setText(_translate("DialogImportH5", "Full scan")) + self.pushButton_browse_filesystem.setText(_translate("DialogImportH5", "Browse Filesystem")) + self.label_6.setText(_translate("DialogImportH5", "to")) + self.label_8.setText(_translate("DialogImportH5", "To Grid Method")) + self.checkBox_use_ideal_xy_values.setToolTip(_translate("DialogImportH5", "

Maps the z-values to the ideal x-y values from the h5 file. Has higher priority then first-image, if both are checked

")) + self.label_10.setToolTip(_translate("DialogImportH5", "

Maps the z-values of all images after the first image to the x-y values of the first image (used to check if it is necessary to track the x-y values for all images)

")) + self.label_10.setText(_translate("DialogImportH5", "First Image X-Y Values")) + self.checkBox_scan_direction_combined.setText(_translate("DialogImportH5", "In and Out combined")) + self.checkBox_use_first_image_xy_values.setToolTip(_translate("DialogImportH5", "

Maps the z-values of all images after the first image to the x-y values of the first image (used to check if it is necessary to track the x-y values for all images)

")) + self.label_3.setToolTip(_translate("DialogImportH5", "

If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters

")) + self.label_3.setText(_translate("DialogImportH5", "Visualyse Window")) + self.label_11.setText(_translate("DialogImportH5", "Ideal X-Y Values")) + self.label_9.setText(_translate("DialogImportH5", "Scan Directions")) + self.checkBox_scan_direction_seperated.setText(_translate("DialogImportH5", "In and Out seperated")) + self.label_2.setText(_translate("DialogImportH5", "Number of Bins")) + self.radioButton_square_scan.setToolTip(_translate("DialogImportH5", "

If square inside circle is chosen only the biggest square inside of the circle is added. This might help with 2d filters

")) + self.radioButton_square_scan.setText(_translate("DialogImportH5", "Square inside of the circle")) + self.checkBox_cut_zoom.setToolTip(_translate("DialogImportH5", "

Removes the frames at the end with a significant smaller range than the images before

")) + self.label_4.setToolTip(_translate("DialogImportH5", "

Removes the frames at the end with a significant smaller range than the images before

")) + self.label_4.setText(_translate("DialogImportH5", "Cut Zoom")) + self.checkBox_scan_direction_in.setText(_translate("DialogImportH5", "In")) + self.checkBox_remove_offset.setToolTip(_translate("DialogImportH5", "

Removes the offset of the z-values (value = value - min(values))

")) + self.label_12.setToolTip(_translate("DialogImportH5", "

If checked and the file has a 4th column the 4th column is used as z-values

")) + self.label_12.setText(_translate("DialogImportH5", "4th column as z")) + self.checkBox_use_4th_as_z.setToolTip(_translate("DialogImportH5", "

If checked and the file has a 4th column the 4th column is used as z-values

")) diff --git a/view/ui/ui_dialog_import_npy.py b/view/ui/ui_dialog_import_npy.py new file mode 100644 index 0000000..bb24722 --- /dev/null +++ b/view/ui/ui_dialog_import_npy.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogImportNpy.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogImportNpy(object): + def setupUi(self, DialogImportNpy): + DialogImportNpy.setObjectName("DialogImportNpy") + DialogImportNpy.resize(636, 425) + self.verticalLayout = QtWidgets.QVBoxLayout(DialogImportNpy) + self.verticalLayout.setObjectName("verticalLayout") + self.widget = QtWidgets.QWidget(DialogImportNpy) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.pushButton_browse_filesystem = QtWidgets.QPushButton(self.widget) + self.pushButton_browse_filesystem.setObjectName("pushButton_browse_filesystem") + self.gridLayout_2.addWidget(self.pushButton_browse_filesystem, 1, 3, 1, 1) + self.label_8 = QtWidgets.QLabel(self.widget) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.comboBox_to_grid_method = QtWidgets.QComboBox(self.widget) + self.comboBox_to_grid_method.setObjectName("comboBox_to_grid_method") + self.gridLayout_2.addWidget(self.comboBox_to_grid_method, 2, 2, 1, 2) + self.lineEdit_file_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.gridLayout_2.addWidget(self.lineEdit_file_path, 1, 2, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 3, 1, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.widget) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.gridLayout_2.addWidget(self.widget_2, 4, 2, 1, 2) + self.spinBox_number_of_bins = QtWidgets.QSpinBox(self.widget) + self.spinBox_number_of_bins.setMaximum(100000) + self.spinBox_number_of_bins.setProperty("value", 100) + self.spinBox_number_of_bins.setObjectName("spinBox_number_of_bins") + self.gridLayout_2.addWidget(self.spinBox_number_of_bins, 3, 2, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 1, 1, 1, 1) + self.verticalLayout.addWidget(self.widget) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(DialogImportNpy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(DialogImportNpy) + self.buttonBox.accepted.connect(DialogImportNpy.accept) + self.buttonBox.rejected.connect(DialogImportNpy.reject) + QtCore.QMetaObject.connectSlotsByName(DialogImportNpy) + + def retranslateUi(self, DialogImportNpy): + _translate = QtCore.QCoreApplication.translate + DialogImportNpy.setWindowTitle(_translate("DialogImportNpy", "Import h5 file")) + self.pushButton_browse_filesystem.setText(_translate("DialogImportNpy", "Browse Filesystem")) + self.label_8.setText(_translate("DialogImportNpy", "To Grid Method")) + self.label_2.setText(_translate("DialogImportNpy", "Number of Bins")) + self.label.setText(_translate("DialogImportNpy", "File Path:")) diff --git a/view/ui/ui_dialog_jump_visualization.py b/view/ui/ui_dialog_jump_visualization.py new file mode 100644 index 0000000..f686e0f --- /dev/null +++ b/view/ui/ui_dialog_jump_visualization.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogJumpVisualization.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(1015, 828) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_analyse_window = QtWidgets.QWidget(self.widget) + self.widget_analyse_window.setObjectName("widget_analyse_window") + self.gridLayout_2.addWidget(self.widget_analyse_window, 0, 0, 1, 2) + self.gridLayout.addWidget(self.widget, 0, 2, 1, 1) + self.widget_2 = QtWidgets.QWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.groupBox = QtWidgets.QGroupBox(self.widget_2) + self.groupBox.setObjectName("groupBox") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_4.addWidget(self.label, 0, 0, 1, 1) + self.listWidget_feature_class = QtWidgets.QListWidget(self.groupBox) + self.listWidget_feature_class.setObjectName("listWidget_feature_class") + self.gridLayout_4.addWidget(self.listWidget_feature_class, 1, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout_4.addWidget(self.label_2, 3, 0, 1, 1) + self.listWidget_feature = QtWidgets.QListWidget(self.groupBox) + self.listWidget_feature.setObjectName("listWidget_feature") + self.gridLayout_4.addWidget(self.listWidget_feature, 4, 0, 1, 1) + self.widget_3 = QtWidgets.QWidget(self.groupBox) + self.widget_3.setObjectName("widget_3") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pushButton_feature_list_check_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_check_all.setObjectName("pushButton_feature_list_check_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_check_all) + self.pushButton_feature_list_invert = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_invert.setObjectName("pushButton_feature_list_invert") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_invert) + self.pushButton_feature_list_uncheck_all = QtWidgets.QPushButton(self.widget_3) + self.pushButton_feature_list_uncheck_all.setObjectName("pushButton_feature_list_uncheck_all") + self.horizontalLayout_2.addWidget(self.pushButton_feature_list_uncheck_all) + self.gridLayout_4.addWidget(self.widget_3, 5, 0, 1, 1) + self.widget_4 = QtWidgets.QWidget(self.groupBox) + self.widget_4.setObjectName("widget_4") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_4) + self.horizontalLayout.setObjectName("horizontalLayout") + self.pushButton_feature_class_check_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_check_all.setObjectName("pushButton_feature_class_check_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_check_all) + self.pushButton_feature_class_invert = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_invert.setObjectName("pushButton_feature_class_invert") + self.horizontalLayout.addWidget(self.pushButton_feature_class_invert) + self.pushButton_feature_class_uncheck_all = QtWidgets.QPushButton(self.widget_4) + self.pushButton_feature_class_uncheck_all.setObjectName("pushButton_feature_class_uncheck_all") + self.horizontalLayout.addWidget(self.pushButton_feature_class_uncheck_all) + self.gridLayout_4.addWidget(self.widget_4, 2, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_3.addItem(spacerItem, 3, 0, 1, 1) + self.groupBox_2 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_2.setObjectName("groupBox_2") + self.formLayout = QtWidgets.QFormLayout(self.groupBox_2) + self.formLayout.setObjectName("formLayout") + self.label_4 = QtWidgets.QLabel(self.groupBox_2) + self.label_4.setObjectName("label_4") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.lineEdit_jump_threshold = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_jump_threshold.setObjectName("lineEdit_jump_threshold") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.lineEdit_jump_threshold) + self.gridLayout_3.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_5.setObjectName("gridLayout_5") + self.textBrowser_jump_information = QtWidgets.QTextBrowser(self.groupBox_3) + self.textBrowser_jump_information.setObjectName("textBrowser_jump_information") + self.gridLayout_5.addWidget(self.textBrowser_jump_information, 0, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_3, 2, 0, 1, 1) + self.gridLayout.addWidget(self.widget_2, 0, 3, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Feature Jump Visualization")) + self.groupBox.setTitle(_translate("Dialog", "Feature")) + self.label.setText(_translate("Dialog", "Feature Class")) + self.label_2.setText(_translate("Dialog", "Feature")) + self.pushButton_feature_list_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_list_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_list_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.pushButton_feature_class_check_all.setText(_translate("Dialog", "Check All")) + self.pushButton_feature_class_invert.setText(_translate("Dialog", "Invert")) + self.pushButton_feature_class_uncheck_all.setText(_translate("Dialog", "Uncheck All")) + self.groupBox_2.setTitle(_translate("Dialog", "Settings")) + self.label_4.setToolTip(_translate("Dialog", "The threshold when a movement is detected as a jump")) + self.label_4.setText(_translate("Dialog", "Jump Threshold:")) + self.lineEdit_jump_threshold.setToolTip(_translate("Dialog", "The threshold when a movement is detected as a jump")) + self.groupBox_3.setTitle(_translate("Dialog", "Jump Information")) diff --git a/view/ui/ui_dialog_line_profile.py b/view/ui/ui_dialog_line_profile.py new file mode 100644 index 0000000..9bf69c2 --- /dev/null +++ b/view/ui/ui_dialog_line_profile.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogLineProfile.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(1207, 777) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_render_window_image = QtWidgets.QWidget(self.widget) + self.widget_render_window_image.setObjectName("widget_render_window_image") + self.gridLayout_2.addWidget(self.widget_render_window_image, 0, 0, 1, 1) + self.widget_render_window_profile = QtWidgets.QWidget(self.widget) + self.widget_render_window_profile.setObjectName("widget_render_window_profile") + self.gridLayout_2.addWidget(self.widget_render_window_profile, 1, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.formLayout = QtWidgets.QFormLayout(self.groupBox) + self.formLayout.setObjectName("formLayout") + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2) + self.widget_2 = QtWidgets.QWidget(self.groupBox) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.radioButton_remove_offset_none = QtWidgets.QRadioButton(self.widget_2) + self.radioButton_remove_offset_none.setObjectName("radioButton_remove_offset_none") + self.horizontalLayout.addWidget(self.radioButton_remove_offset_none) + self.radioButton_remove_offset_whole_image = QtWidgets.QRadioButton(self.widget_2) + self.radioButton_remove_offset_whole_image.setObjectName("radioButton_remove_offset_whole_image") + self.horizontalLayout.addWidget(self.radioButton_remove_offset_whole_image) + self.radioButton_remove_offset_line_profile = QtWidgets.QRadioButton(self.widget_2) + self.radioButton_remove_offset_line_profile.setObjectName("radioButton_remove_offset_line_profile") + self.horizontalLayout.addWidget(self.radioButton_remove_offset_line_profile) + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.widget_2) + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label) + self.lineEdit_hight_profile_scan_formula = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_hight_profile_scan_formula.setObjectName("lineEdit_hight_profile_scan_formula") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_hight_profile_scan_formula) + self.label_3 = QtWidgets.QLabel(self.groupBox) + self.label_3.setObjectName("label_3") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3) + self.comboBox_x_axis_prefix = QtWidgets.QComboBox(self.groupBox) + self.comboBox_x_axis_prefix.setObjectName("comboBox_x_axis_prefix") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.comboBox_x_axis_prefix) + self.label_4 = QtWidgets.QLabel(self.groupBox) + self.label_4.setObjectName("label_4") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.comboBox_y_axis_prefix = QtWidgets.QComboBox(self.groupBox) + self.comboBox_y_axis_prefix.setObjectName("comboBox_y_axis_prefix") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.comboBox_y_axis_prefix) + self.label_5 = QtWidgets.QLabel(self.groupBox) + self.label_5.setObjectName("label_5") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_5) + self.label_6 = QtWidgets.QLabel(self.groupBox) + self.label_6.setObjectName("label_6") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_6) + self.widget_3 = QtWidgets.QWidget(self.groupBox) + self.widget_3.setObjectName("widget_3") + self.formLayout.setWidget(12, QtWidgets.QFormLayout.LabelRole, self.widget_3) + self.lineEdit_line_movement_speed = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_line_movement_speed.setObjectName("lineEdit_line_movement_speed") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.lineEdit_line_movement_speed) + self.lineEdit_line_rotation_speed = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_line_rotation_speed.setObjectName("lineEdit_line_rotation_speed") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.lineEdit_line_rotation_speed) + self.listWidget_lines = QtWidgets.QListWidget(self.groupBox) + self.listWidget_lines.setObjectName("listWidget_lines") + self.formLayout.setWidget(7, QtWidgets.QFormLayout.SpanningRole, self.listWidget_lines) + self.pushButton_calculate_new_step_size = QtWidgets.QPushButton(self.groupBox) + self.pushButton_calculate_new_step_size.setObjectName("pushButton_calculate_new_step_size") + self.formLayout.setWidget(10, QtWidgets.QFormLayout.SpanningRole, self.pushButton_calculate_new_step_size) + self.pushButton_add_line = QtWidgets.QPushButton(self.groupBox) + self.pushButton_add_line.setObjectName("pushButton_add_line") + self.formLayout.setWidget(8, QtWidgets.QFormLayout.SpanningRole, self.pushButton_add_line) + self.pushButton_remove_line = QtWidgets.QPushButton(self.groupBox) + self.pushButton_remove_line.setObjectName("pushButton_remove_line") + self.formLayout.setWidget(9, QtWidgets.QFormLayout.SpanningRole, self.pushButton_remove_line) + self.pushButton_export_line_profile = QtWidgets.QPushButton(self.groupBox) + self.pushButton_export_line_profile.setObjectName("pushButton_export_line_profile") + self.formLayout.setWidget(11, QtWidgets.QFormLayout.SpanningRole, self.pushButton_export_line_profile) + self.label_7 = QtWidgets.QLabel(self.groupBox) + self.label_7.setObjectName("label_7") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.SpanningRole, self.label_7) + self.gridLayout_2.addWidget(self.groupBox, 0, 1, 1, 1) + self.gridLayout.addWidget(self.widget, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox.setTitle(_translate("Dialog", "Line Profile")) + self.label_2.setText(_translate("Dialog", "Remove Line Profile Offset")) + self.radioButton_remove_offset_none.setText(_translate("Dialog", "None")) + self.radioButton_remove_offset_whole_image.setText(_translate("Dialog", "Whole Image")) + self.radioButton_remove_offset_line_profile.setText(_translate("Dialog", "Line Profile Range")) + self.label.setText(_translate("Dialog", "Height Profile Scale")) + self.label_3.setText(_translate("Dialog", "X Axis Unit Prefix")) + self.label_4.setText(_translate("Dialog", "Y Axis Unit Prefix")) + self.label_5.setToolTip(_translate("Dialog", "The drawn line can be moved with the arrow keys")) + self.label_5.setText(_translate("Dialog", "Line Movement Speed")) + self.label_6.setToolTip(_translate("Dialog", "The line can be rotated with the \"insert\" and \"delete\" key")) + self.label_6.setText(_translate("Dialog", "Line Rotation Speed")) + self.pushButton_calculate_new_step_size.setText(_translate("Dialog", "Calculate new step size")) + self.pushButton_add_line.setText(_translate("Dialog", "Add Line")) + self.pushButton_remove_line.setText(_translate("Dialog", "Remove Line")) + self.pushButton_export_line_profile.setText(_translate("Dialog", "Export Dialog")) + self.label_7.setText(_translate("Dialog", "Use Arrow Keys to move the selected line, insert and delete for rotation")) diff --git a/view/ui/ui_dialog_measuring_points.py b/view/ui/ui_dialog_measuring_points.py new file mode 100644 index 0000000..46c4ac7 --- /dev/null +++ b/view/ui/ui_dialog_measuring_points.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogMeasuringPoints.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(864, 1194) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_2 = QtWidgets.QWidget(Dialog) + self.widget_2.setMaximumSize(QtCore.QSize(500, 16777215)) + self.widget_2.setObjectName("widget_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_2.setObjectName("gridLayout_2") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 5, 0, 1, 1) + self.groupBox_2 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_5.setObjectName("gridLayout_5") + self.doubleSpinBox_ideal_measuring_points_size = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.doubleSpinBox_ideal_measuring_points_size.setMaximum(100000.0) + self.doubleSpinBox_ideal_measuring_points_size.setProperty("value", 1.0) + self.doubleSpinBox_ideal_measuring_points_size.setObjectName("doubleSpinBox_ideal_measuring_points_size") + self.gridLayout_5.addWidget(self.doubleSpinBox_ideal_measuring_points_size, 2, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_2) + self.label_7.setObjectName("label_7") + self.gridLayout_5.addWidget(self.label_7, 1, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.groupBox_2) + self.label_4.setObjectName("label_4") + self.gridLayout_5.addWidget(self.label_4, 3, 0, 1, 1) + self.pushButton_ideal_measuring_points_color = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_ideal_measuring_points_color.setObjectName("pushButton_ideal_measuring_points_color") + self.gridLayout_5.addWidget(self.pushButton_ideal_measuring_points_color, 3, 1, 1, 1) + self.comboBox_ideal_measuring_points_plot_type = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_ideal_measuring_points_plot_type.setObjectName("comboBox_ideal_measuring_points_plot_type") + self.gridLayout_5.addWidget(self.comboBox_ideal_measuring_points_plot_type, 1, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.groupBox_2) + self.label_3.setObjectName("label_3") + self.gridLayout_5.addWidget(self.label_3, 2, 0, 1, 1) + self.checkBox_show_ideal_measuring_points = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_show_ideal_measuring_points.setObjectName("checkBox_show_ideal_measuring_points") + self.gridLayout_5.addWidget(self.checkBox_show_ideal_measuring_points, 0, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_2, 3, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.widget_2) + self.groupBox.setObjectName("groupBox") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_4.setObjectName("gridLayout_4") + self.pushButton_real_point_animate = QtWidgets.QPushButton(self.groupBox) + self.pushButton_real_point_animate.setObjectName("pushButton_real_point_animate") + self.gridLayout_4.addWidget(self.pushButton_real_point_animate, 5, 0, 1, 1) + self.widget = QtWidgets.QWidget(self.groupBox) + self.widget.setObjectName("widget") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_3.setObjectName("gridLayout_3") + self.doubleSpinBox_real_measuring_points_size = QtWidgets.QDoubleSpinBox(self.widget) + self.doubleSpinBox_real_measuring_points_size.setMaximum(100000.0) + self.doubleSpinBox_real_measuring_points_size.setProperty("value", 1.0) + self.doubleSpinBox_real_measuring_points_size.setObjectName("doubleSpinBox_real_measuring_points_size") + self.gridLayout_3.addWidget(self.doubleSpinBox_real_measuring_points_size, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_3.addWidget(self.label, 1, 0, 1, 1) + self.pushButton_real_measuring_points_color = QtWidgets.QPushButton(self.widget) + self.pushButton_real_measuring_points_color.setObjectName("pushButton_real_measuring_points_color") + self.gridLayout_3.addWidget(self.pushButton_real_measuring_points_color, 2, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_3.addWidget(self.label_2, 2, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setObjectName("label_6") + self.gridLayout_3.addWidget(self.label_6, 0, 0, 1, 1) + self.comboBox_real_measuring_points_plot_type = QtWidgets.QComboBox(self.widget) + self.comboBox_real_measuring_points_plot_type.setObjectName("comboBox_real_measuring_points_plot_type") + self.gridLayout_3.addWidget(self.comboBox_real_measuring_points_plot_type, 0, 1, 1, 1) + self.gridLayout_4.addWidget(self.widget, 3, 0, 1, 1) + self.radioButton_show_measuring_points = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_show_measuring_points.setObjectName("radioButton_show_measuring_points") + self.gridLayout_4.addWidget(self.radioButton_show_measuring_points, 0, 0, 1, 1) + self.radioButton_show_measuring_points_velocity = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_show_measuring_points_velocity.setObjectName("radioButton_show_measuring_points_velocity") + self.gridLayout_4.addWidget(self.radioButton_show_measuring_points_velocity, 1, 0, 1, 1) + self.radioButton_show_measuring_points_none = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_show_measuring_points_none.setObjectName("radioButton_show_measuring_points_none") + self.gridLayout_4.addWidget(self.radioButton_show_measuring_points_none, 2, 0, 1, 1) + self.widget_7 = QtWidgets.QWidget(self.groupBox) + self.widget_7.setObjectName("widget_7") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_7) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_11 = QtWidgets.QLabel(self.widget_7) + self.label_11.setObjectName("label_11") + self.horizontalLayout.addWidget(self.label_11) + self.spinBox_points_to_skip = QtWidgets.QSpinBox(self.widget_7) + self.spinBox_points_to_skip.setMaximum(1000) + self.spinBox_points_to_skip.setProperty("value", 10) + self.spinBox_points_to_skip.setObjectName("spinBox_points_to_skip") + self.horizontalLayout.addWidget(self.spinBox_points_to_skip) + self.gridLayout_4.addWidget(self.widget_7, 4, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox, 2, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_6.setObjectName("gridLayout_6") + self.widget_3 = QtWidgets.QWidget(self.groupBox_3) + self.widget_3.setObjectName("widget_3") + self.gridLayout_7 = QtWidgets.QGridLayout(self.widget_3) + self.gridLayout_7.setObjectName("gridLayout_7") + self.label_5 = QtWidgets.QLabel(self.widget_3) + self.label_5.setObjectName("label_5") + self.gridLayout_7.addWidget(self.label_5, 0, 0, 1, 1) + self.spinBox_image_number = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_image_number.setMaximum(100000) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_7.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.gridLayout_6.addWidget(self.widget_3, 0, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_3, 0, 0, 1, 1) + self.groupBox_4 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_8 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_8.setObjectName("gridLayout_8") + self.checkBox_show_image = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_show_image.setObjectName("checkBox_show_image") + self.gridLayout_8.addWidget(self.checkBox_show_image, 0, 0, 1, 1) + self.widget_4 = QtWidgets.QWidget(self.groupBox_4) + self.widget_4.setObjectName("widget_4") + self.gridLayout_9 = QtWidgets.QGridLayout(self.widget_4) + self.gridLayout_9.setObjectName("gridLayout_9") + self.label_8 = QtWidgets.QLabel(self.widget_4) + self.label_8.setObjectName("label_8") + self.gridLayout_9.addWidget(self.label_8, 0, 0, 1, 1) + self.comboBox_image_color_map = QtWidgets.QComboBox(self.widget_4) + self.comboBox_image_color_map.setObjectName("comboBox_image_color_map") + self.gridLayout_9.addWidget(self.comboBox_image_color_map, 0, 1, 1, 1) + self.gridLayout_8.addWidget(self.widget_4, 1, 0, 1, 1) + self.widget_5 = QtWidgets.QWidget(self.groupBox_4) + self.widget_5.setObjectName("widget_5") + self.gridLayout_10 = QtWidgets.QGridLayout(self.widget_5) + self.gridLayout_10.setObjectName("gridLayout_10") + self.label_9 = QtWidgets.QLabel(self.widget_5) + self.label_9.setObjectName("label_9") + self.gridLayout_10.addWidget(self.label_9, 0, 0, 1, 1) + self.pushButton_image_nan_color = QtWidgets.QPushButton(self.widget_5) + self.pushButton_image_nan_color.setObjectName("pushButton_image_nan_color") + self.gridLayout_10.addWidget(self.pushButton_image_nan_color, 0, 1, 1, 1) + self.gridLayout_8.addWidget(self.widget_5, 2, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_4, 1, 0, 1, 1) + self.groupBox_5 = QtWidgets.QGroupBox(self.widget_2) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_11.setObjectName("gridLayout_11") + self.textBrowser_selection_information = QtWidgets.QTextBrowser(self.groupBox_5) + self.textBrowser_selection_information.setObjectName("textBrowser_selection_information") + self.gridLayout_11.addWidget(self.textBrowser_selection_information, 1, 0, 1, 1) + self.pushButton_show_time_diagram = QtWidgets.QPushButton(self.groupBox_5) + self.pushButton_show_time_diagram.setObjectName("pushButton_show_time_diagram") + self.gridLayout_11.addWidget(self.pushButton_show_time_diagram, 2, 0, 1, 1) + self.pushButton_show_velocity_diagram = QtWidgets.QPushButton(self.groupBox_5) + self.pushButton_show_velocity_diagram.setObjectName("pushButton_show_velocity_diagram") + self.gridLayout_11.addWidget(self.pushButton_show_velocity_diagram, 3, 0, 1, 1) + self.pushButton_select_all = QtWidgets.QPushButton(self.groupBox_5) + self.pushButton_select_all.setObjectName("pushButton_select_all") + self.gridLayout_11.addWidget(self.pushButton_select_all, 0, 0, 1, 1) + self.pushButton_ideal_points_show_velocity_diagram = QtWidgets.QPushButton(self.groupBox_5) + self.pushButton_ideal_points_show_velocity_diagram.setObjectName("pushButton_ideal_points_show_velocity_diagram") + self.gridLayout_11.addWidget(self.pushButton_ideal_points_show_velocity_diagram, 4, 0, 1, 1) + self.groupBox_6 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_6.setObjectName("groupBox_6") + self.gridLayout_12 = QtWidgets.QGridLayout(self.groupBox_6) + self.gridLayout_12.setObjectName("gridLayout_12") + self.widget_6 = QtWidgets.QWidget(self.groupBox_6) + self.widget_6.setObjectName("widget_6") + self.gridLayout_12.addWidget(self.widget_6, 2, 0, 1, 1) + self.label_10 = QtWidgets.QLabel(self.groupBox_6) + self.label_10.setObjectName("label_10") + self.gridLayout_12.addWidget(self.label_10, 0, 0, 1, 2) + self.radioButton_angular_velocity_middle_point = QtWidgets.QRadioButton(self.groupBox_6) + self.radioButton_angular_velocity_middle_point.setChecked(False) + self.radioButton_angular_velocity_middle_point.setObjectName("radioButton_angular_velocity_middle_point") + self.gridLayout_12.addWidget(self.radioButton_angular_velocity_middle_point, 1, 0, 1, 1) + self.spinBox_angular_velocity_point_index = QtWidgets.QSpinBox(self.groupBox_6) + self.spinBox_angular_velocity_point_index.setObjectName("spinBox_angular_velocity_point_index") + self.gridLayout_12.addWidget(self.spinBox_angular_velocity_point_index, 1, 2, 1, 1) + self.radioButton_angular_velocity_point_index = QtWidgets.QRadioButton(self.groupBox_6) + self.radioButton_angular_velocity_point_index.setChecked(True) + self.radioButton_angular_velocity_point_index.setObjectName("radioButton_angular_velocity_point_index") + self.gridLayout_12.addWidget(self.radioButton_angular_velocity_point_index, 1, 1, 1, 1) + self.pushButton_plot_angular_velocity = QtWidgets.QPushButton(self.groupBox_6) + self.pushButton_plot_angular_velocity.setObjectName("pushButton_plot_angular_velocity") + self.gridLayout_12.addWidget(self.pushButton_plot_angular_velocity, 3, 0, 1, 3) + self.pushButton_plot_ideal_angular_velocity = QtWidgets.QPushButton(self.groupBox_6) + self.pushButton_plot_ideal_angular_velocity.setObjectName("pushButton_plot_ideal_angular_velocity") + self.gridLayout_12.addWidget(self.pushButton_plot_ideal_angular_velocity, 4, 0, 1, 3) + self.gridLayout_11.addWidget(self.groupBox_6, 5, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_5, 4, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(self.widget_2) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout_2.addWidget(self.buttonBox, 6, 0, 1, 1) + self.gridLayout.addWidget(self.widget_2, 0, 1, 1, 1) + self.widget_render_window = QtWidgets.QWidget(Dialog) + self.widget_render_window.setObjectName("widget_render_window") + self.gridLayout.addWidget(self.widget_render_window, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox_2.setTitle(_translate("Dialog", "Ideal Measuring Points")) + self.label_7.setText(_translate("Dialog", "Plot")) + self.label_4.setText(_translate("Dialog", "Line Color")) + self.pushButton_ideal_measuring_points_color.setText(_translate("Dialog", "Color")) + self.label_3.setText(_translate("Dialog", "Size")) + self.checkBox_show_ideal_measuring_points.setText(_translate("Dialog", "Show Ideal Measuring Points")) + self.groupBox.setTitle(_translate("Dialog", "Real Measuring Points")) + self.pushButton_real_point_animate.setText(_translate("Dialog", "Animate")) + self.label.setText(_translate("Dialog", "Size")) + self.pushButton_real_measuring_points_color.setText(_translate("Dialog", "Color")) + self.label_2.setText(_translate("Dialog", "Line Color")) + self.label_6.setText(_translate("Dialog", "Plot")) + self.radioButton_show_measuring_points.setText(_translate("Dialog", "Show Measuring Points Z-Value")) + self.radioButton_show_measuring_points_velocity.setText(_translate("Dialog", "Show Measuring Points Velocity")) + self.radioButton_show_measuring_points_none.setText(_translate("Dialog", "None")) + self.label_11.setText(_translate("Dialog", "Points to skip for animation")) + self.groupBox_3.setTitle(_translate("Dialog", "Visualization")) + self.label_5.setText(_translate("Dialog", "Image Number")) + self.groupBox_4.setTitle(_translate("Dialog", "Image")) + self.checkBox_show_image.setText(_translate("Dialog", "Show Image")) + self.label_8.setText(_translate("Dialog", "Image Color Map")) + self.label_9.setText(_translate("Dialog", "NaN Color")) + self.pushButton_image_nan_color.setText(_translate("Dialog", "Color")) + self.groupBox_5.setTitle(_translate("Dialog", "Selection Information")) + self.pushButton_show_time_diagram.setText(_translate("Dialog", "Show Time Diagram")) + self.pushButton_show_velocity_diagram.setText(_translate("Dialog", "Show Velocity Diagram")) + self.pushButton_select_all.setText(_translate("Dialog", "Select All")) + self.pushButton_ideal_points_show_velocity_diagram.setText(_translate("Dialog", "Show Ideal Points Velocity Diagram")) + self.groupBox_6.setTitle(_translate("Dialog", "Plot Angle Velocity")) + self.label_10.setText(_translate("Dialog", "Spiral Middle Point")) + self.radioButton_angular_velocity_middle_point.setToolTip(_translate("Dialog", "

Calculates the middle point from the range of the datapoints

")) + self.radioButton_angular_velocity_middle_point.setText(_translate("Dialog", "Middle Point")) + self.spinBox_angular_velocity_point_index.setToolTip(_translate("Dialog", "

Choose a point index which is treated as the middle point of the spiral. Index -1 is last point

")) + self.radioButton_angular_velocity_point_index.setToolTip(_translate("Dialog", "

Choose a point index which is treated as the middle point of the spiral. Index -1 is last point

")) + self.radioButton_angular_velocity_point_index.setText(_translate("Dialog", "Point Index")) + self.pushButton_plot_angular_velocity.setText(_translate("Dialog", "Plot Angular Velocity Diagram")) + self.pushButton_plot_ideal_angular_velocity.setText(_translate("Dialog", "Plot Ideal Angular Velocity Diagram")) diff --git a/view/ui/ui_dialog_neural_networks.py b/view/ui/ui_dialog_neural_networks.py new file mode 100644 index 0000000..d7426a5 --- /dev/null +++ b/view/ui/ui_dialog_neural_networks.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogNeuralNetworks.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(493, 765) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.lineEdit_weight_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_weight_path.setObjectName("lineEdit_weight_path") + self.gridLayout_2.addWidget(self.lineEdit_weight_path, 2, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 2, 0, 1, 1) + self.lineEdit_save_training_data = QtWidgets.QLineEdit(self.widget) + self.lineEdit_save_training_data.setObjectName("lineEdit_save_training_data") + self.gridLayout_2.addWidget(self.lineEdit_save_training_data, 3, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.widget) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 4, 0, 1, 1) + self.spinBox_epochs = QtWidgets.QSpinBox(self.widget) + self.spinBox_epochs.setMinimum(1) + self.spinBox_epochs.setMaximum(99999999) + self.spinBox_epochs.setObjectName("spinBox_epochs") + self.gridLayout_2.addWidget(self.spinBox_epochs, 4, 1, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 14, 0, 1, 1) + self.lineEdit_yolo_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_yolo_path.setObjectName("lineEdit_yolo_path") + self.gridLayout_2.addWidget(self.lineEdit_yolo_path, 0, 1, 1, 1) + self.textBrowser = QtWidgets.QTextBrowser(self.widget) + self.textBrowser.setObjectName("textBrowser") + self.gridLayout_2.addWidget(self.textBrowser, 13, 0, 1, 3) + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 7, 0, 1, 1) + self.pushButton_weights_path = QtWidgets.QPushButton(self.widget) + self.pushButton_weights_path.setObjectName("pushButton_weights_path") + self.gridLayout_2.addWidget(self.pushButton_weights_path, 2, 2, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(self.widget) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 3, 0, 1, 1) + self.pushButton_yolo_path = QtWidgets.QPushButton(self.widget) + self.pushButton_yolo_path.setObjectName("pushButton_yolo_path") + self.gridLayout_2.addWidget(self.pushButton_yolo_path, 0, 2, 1, 1) + self.pushButton_training_data_path = QtWidgets.QPushButton(self.widget) + self.pushButton_training_data_path.setObjectName("pushButton_training_data_path") + self.gridLayout_2.addWidget(self.pushButton_training_data_path, 3, 2, 1, 1) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 8, 0, 1, 1) + self.lineEdit_additional_parameters = QtWidgets.QLineEdit(self.widget) + self.lineEdit_additional_parameters.setObjectName("lineEdit_additional_parameters") + self.gridLayout_2.addWidget(self.lineEdit_additional_parameters, 8, 1, 1, 2) + self.lineEdit_name = QtWidgets.QLineEdit(self.widget) + self.lineEdit_name.setObjectName("lineEdit_name") + self.gridLayout_2.addWidget(self.lineEdit_name, 7, 1, 1, 2) + self.label_7 = QtWidgets.QLabel(self.widget) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 5, 0, 1, 1) + self.spinBox_batch_size = QtWidgets.QSpinBox(self.widget) + self.spinBox_batch_size.setMaximum(200) + self.spinBox_batch_size.setProperty("value", 16) + self.spinBox_batch_size.setObjectName("spinBox_batch_size") + self.gridLayout_2.addWidget(self.spinBox_batch_size, 5, 1, 1, 1) + self.lineEdit_virtual_environment_path = QtWidgets.QLineEdit(self.widget) + self.lineEdit_virtual_environment_path.setObjectName("lineEdit_virtual_environment_path") + self.gridLayout_2.addWidget(self.lineEdit_virtual_environment_path, 1, 1, 1, 1) + self.pushButton_show_detection_command = QtWidgets.QPushButton(self.widget) + self.pushButton_show_detection_command.setObjectName("pushButton_show_detection_command") + self.gridLayout_2.addWidget(self.pushButton_show_detection_command, 12, 2, 1, 1) + self.pushButton_start_training = QtWidgets.QPushButton(self.widget) + self.pushButton_start_training.setObjectName("pushButton_start_training") + self.gridLayout_2.addWidget(self.pushButton_start_training, 10, 0, 1, 2) + self.pushButton_detect = QtWidgets.QPushButton(self.widget) + self.pushButton_detect.setObjectName("pushButton_detect") + self.gridLayout_2.addWidget(self.pushButton_detect, 12, 0, 1, 2) + self.label_8 = QtWidgets.QLabel(self.widget) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 1, 0, 1, 1) + self.pushButton_virutal_enviorment_path = QtWidgets.QPushButton(self.widget) + self.pushButton_virutal_enviorment_path.setObjectName("pushButton_virutal_enviorment_path") + self.gridLayout_2.addWidget(self.pushButton_virutal_enviorment_path, 1, 2, 1, 1) + self.pushButton_show_training_command = QtWidgets.QPushButton(self.widget) + self.pushButton_show_training_command.setObjectName("pushButton_show_training_command") + self.gridLayout_2.addWidget(self.pushButton_show_training_command, 10, 2, 1, 1) + self.doubleSpinBox_confidence_threshold = QtWidgets.QDoubleSpinBox(self.widget) + self.doubleSpinBox_confidence_threshold.setMaximum(1.0) + self.doubleSpinBox_confidence_threshold.setSingleStep(0.05) + self.doubleSpinBox_confidence_threshold.setProperty("value", 0.5) + self.doubleSpinBox_confidence_threshold.setObjectName("doubleSpinBox_confidence_threshold") + self.gridLayout_2.addWidget(self.doubleSpinBox_confidence_threshold, 6, 1, 1, 1) + self.label_9 = QtWidgets.QLabel(self.widget) + self.label_9.setObjectName("label_9") + self.gridLayout_2.addWidget(self.label_9, 6, 0, 1, 1) + self.gridLayout.addWidget(self.widget, 1, 0, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.lineEdit_weight_path.setToolTip(_translate("Dialog", "

Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository

")) + self.label_2.setToolTip(_translate("Dialog", "

Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository

")) + self.label_2.setText(_translate("Dialog", "Starting Weights Path")) + self.lineEdit_save_training_data.setToolTip(_translate("Dialog", "

The training data will be generated from the images and the features

")) + self.label_4.setToolTip(_translate("Dialog", "

Number of Training Iterations

")) + self.label_4.setText(_translate("Dialog", "Number of Epochs")) + self.spinBox_epochs.setToolTip(_translate("Dialog", "

Number of Training Iterations

")) + self.lineEdit_yolo_path.setToolTip(_translate("Dialog", "

Yolo Path should point to the yolov5 git repository

")) + self.label_5.setToolTip(_translate("Dialog", "

Results will be saved to runs/{train|detect}/{name}{increasing number} in the yolov5 folder

")) + self.label_5.setText(_translate("Dialog", "Name")) + self.pushButton_weights_path.setToolTip(_translate("Dialog", "

Path to the weights of the neural network, if empty will take the yolo5s weights given from the repository

")) + self.pushButton_weights_path.setText(_translate("Dialog", "Browse Filesystem")) + self.label.setToolTip(_translate("Dialog", "

Yolo Path should point to the yolov5 git repository

")) + self.label.setText(_translate("Dialog", "Yolo Path")) + self.label_3.setToolTip(_translate("Dialog", "

Path where the training/detection data should be saved

")) + self.label_3.setText(_translate("Dialog", "Data Path")) + self.pushButton_yolo_path.setToolTip(_translate("Dialog", "

Yolo Path should point to the yolov5 git repository

")) + self.pushButton_yolo_path.setText(_translate("Dialog", "Browse Filesystem")) + self.pushButton_training_data_path.setToolTip(_translate("Dialog", "

The training data will be generated from the images and the features

")) + self.pushButton_training_data_path.setText(_translate("Dialog", "Browse Filesystem")) + self.label_6.setText(_translate("Dialog", "Additional parameters")) + self.lineEdit_name.setToolTip(_translate("Dialog", "

Results will be saved to runs/{train|detect}/{name}{increasing number} in the yolov5 folder

")) + self.label_7.setToolTip(_translate("Dialog", "

"Friends don’t let friends use mini-batches larger than 32“")) + self.label_7.setText(_translate("Dialog", "Batch Size")) + self.spinBox_batch_size.setToolTip(_translate("Dialog", "

"Friends don’t let friends use mini-batches larger than 32“")) + self.lineEdit_virtual_environment_path.setToolTip(_translate("Dialog", "

Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python

")) + self.pushButton_show_detection_command.setText(_translate("Dialog", "Show Command")) + self.pushButton_start_training.setText(_translate("Dialog", "Train")) + self.pushButton_detect.setText(_translate("Dialog", "Detect")) + self.label_8.setToolTip(_translate("Dialog", "

Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python

")) + self.label_8.setText(_translate("Dialog", "Python Interpreter Path")) + self.pushButton_virutal_enviorment_path.setToolTip(_translate("Dialog", "

Should point to a python interpreter which has all necessary libraries for yolov5 installed. If its empty it will look in {yolo_path}/venv/bin/python

")) + self.pushButton_virutal_enviorment_path.setText(_translate("Dialog", "Browse Filesystem")) + self.pushButton_show_training_command.setText(_translate("Dialog", "Show Command")) + self.doubleSpinBox_confidence_threshold.setToolTip(_translate("Dialog", "

Confidence threshold for detection


")) + self.label_9.setToolTip(_translate("Dialog", "

Confidence threshold for detection


")) + self.label_9.setText(_translate("Dialog", "Confidence Threshold")) diff --git a/view/ui/ui_dialog_plotable.py b/view/ui/ui_dialog_plotable.py new file mode 100644 index 0000000..c758a62 --- /dev/null +++ b/view/ui/ui_dialog_plotable.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'PlotableDialog.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(903, 573) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.groupBox = QtWidgets.QGroupBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.comboBox_plot_information = QtWidgets.QComboBox(self.groupBox) + self.comboBox_plot_information.setObjectName("comboBox_plot_information") + self.gridLayout_2.addWidget(self.comboBox_plot_information, 0, 1, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 2, 1, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.checkBox_show_empty_data_sets = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_show_empty_data_sets.setText("") + self.checkBox_show_empty_data_sets.setObjectName("checkBox_show_empty_data_sets") + self.gridLayout_2.addWidget(self.checkBox_show_empty_data_sets, 1, 1, 1, 1) + self.gridLayout.addWidget(self.groupBox, 0, 2, 1, 1) + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout.addWidget(self.widget, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 2, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox.setTitle(_translate("Dialog", "Settings")) + self.label.setText(_translate("Dialog", "Information")) + self.label_2.setText(_translate("Dialog", "Show Empty Datasets")) diff --git a/view/ui/ui_dialog_point_cloud.py b/view/ui/ui_dialog_point_cloud.py new file mode 100644 index 0000000..a67601f --- /dev/null +++ b/view/ui/ui_dialog_point_cloud.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogPointCloud.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(377, 602) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_render_window = QtWidgets.QWidget(Dialog) + self.widget_render_window.setObjectName("widget_render_window") + self.gridLayout.addWidget(self.widget_render_window, 0, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 5, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.groupBox) + self.widget_2.setObjectName("widget_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.doubleSpinBox_percentile_min = QtWidgets.QDoubleSpinBox(self.widget_2) + self.doubleSpinBox_percentile_min.setObjectName("doubleSpinBox_percentile_min") + self.gridLayout_3.addWidget(self.doubleSpinBox_percentile_min, 0, 0, 1, 1) + self.doubleSpinBox_percentile_max = QtWidgets.QDoubleSpinBox(self.widget_2) + self.doubleSpinBox_percentile_max.setMaximum(100.0) + self.doubleSpinBox_percentile_max.setProperty("value", 100.0) + self.doubleSpinBox_percentile_max.setObjectName("doubleSpinBox_percentile_max") + self.gridLayout_3.addWidget(self.doubleSpinBox_percentile_max, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.widget_2, 5, 1, 1, 1) + self.checkBox_grey_color_map = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_grey_color_map.setObjectName("checkBox_grey_color_map") + self.gridLayout_2.addWidget(self.checkBox_grey_color_map, 4, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 6, 0, 1, 1) + self.checkBox_show_points = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_show_points.setObjectName("checkBox_show_points") + self.gridLayout_2.addWidget(self.checkBox_show_points, 3, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 1, 0, 1, 1) + self.doubleSpinBox_z_factor = QtWidgets.QDoubleSpinBox(self.groupBox) + self.doubleSpinBox_z_factor.setDecimals(3) + self.doubleSpinBox_z_factor.setMaximum(10000.0) + self.doubleSpinBox_z_factor.setProperty("value", 1.0) + self.doubleSpinBox_z_factor.setObjectName("doubleSpinBox_z_factor") + self.gridLayout_2.addWidget(self.doubleSpinBox_z_factor, 1, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.groupBox) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1) + self.spinBox_image_number = QtWidgets.QSpinBox(self.groupBox) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_2.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.gridLayout.addWidget(self.groupBox, 0, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 1, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox.setTitle(_translate("Dialog", "GroupBox")) + self.label_2.setText(_translate("Dialog", "Color Map Percentile")) + self.checkBox_grey_color_map.setText(_translate("Dialog", "Grey Color Map")) + self.checkBox_show_points.setText(_translate("Dialog", "Show Points")) + self.label.setText(_translate("Dialog", "Z Scale Factor")) + self.label_3.setText(_translate("Dialog", "Image Number")) diff --git a/view/ui/ui_dialog_progress.py b/view/ui/ui_dialog_progress.py new file mode 100644 index 0000000..969c9df --- /dev/null +++ b/view/ui/ui_dialog_progress.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogProgress.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 300) + Dialog.setWindowTitle("") + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.label_progress_text = QtWidgets.QLabel(Dialog) + self.label_progress_text.setObjectName("label_progress_text") + self.gridLayout.addWidget(self.label_progress_text, 0, 0, 1, 1) + self.progressBar = QtWidgets.QProgressBar(Dialog) + self.progressBar.setProperty("value", 24) + self.progressBar.setObjectName("progressBar") + self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 1) + self.pushButton_cancel = QtWidgets.QPushButton(Dialog) + self.pushButton_cancel.setObjectName("pushButton_cancel") + self.gridLayout.addWidget(self.pushButton_cancel, 1, 1, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + self.label_progress_text.setText(_translate("Dialog", "Test test")) + self.pushButton_cancel.setText(_translate("Dialog", "Cancel")) diff --git a/view/ui/ui_dialog_rescale_features.py b/view/ui/ui_dialog_rescale_features.py new file mode 100644 index 0000000..54cbf55 --- /dev/null +++ b/view/ui/ui_dialog_rescale_features.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogRescaleFeatures.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.lineEdit_y_factor = QtWidgets.QLineEdit(self.widget) + self.lineEdit_y_factor.setObjectName("lineEdit_y_factor") + self.gridLayout_2.addWidget(self.lineEdit_y_factor, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.lineEdit_x_factor = QtWidgets.QLineEdit(self.widget) + self.lineEdit_x_factor.setObjectName("lineEdit_x_factor") + self.gridLayout_2.addWidget(self.lineEdit_x_factor, 0, 1, 1, 1) + self.pushButton_auto_scale = QtWidgets.QPushButton(self.widget) + self.pushButton_auto_scale.setObjectName("pushButton_auto_scale") + self.gridLayout_2.addWidget(self.pushButton_auto_scale, 2, 0, 1, 2) + self.gridLayout.addWidget(self.widget, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_2.setText(_translate("Dialog", "y scale factor")) + self.label.setText(_translate("Dialog", "x scale factor")) + self.pushButton_auto_scale.setText(_translate("Dialog", "Autoscale for old files")) diff --git a/view/ui/ui_dialog_saasmi.py b/view/ui/ui_dialog_saasmi.py new file mode 100644 index 0000000..c896500 --- /dev/null +++ b/view/ui/ui_dialog_saasmi.py @@ -0,0 +1,545 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogSaasmi.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(1288, 1186) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 4, 1, 2) + self.scrollArea = QtWidgets.QScrollArea(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) + self.scrollArea.setSizePolicy(sizePolicy) + self.scrollArea.setMinimumSize(QtCore.QSize(500, 0)) + self.scrollArea.setMaximumSize(QtCore.QSize(500, 16777215)) + self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents_2 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, -868, 484, 2003)) + self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2") + self.gridLayout_12 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_2) + self.gridLayout_12.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.gridLayout_12.setHorizontalSpacing(10) + self.gridLayout_12.setObjectName("gridLayout_12") + self.comboBox_graph_selection = QtWidgets.QComboBox(self.scrollAreaWidgetContents_2) + self.comboBox_graph_selection.setObjectName("comboBox_graph_selection") + self.gridLayout_12.addWidget(self.comboBox_graph_selection, 1, 0, 1, 1) + self.tabWidget_saasmi = QtWidgets.QTabWidget(self.scrollAreaWidgetContents_2) + self.tabWidget_saasmi.setObjectName("tabWidget_saasmi") + self.tab_graph = QtWidgets.QWidget() + self.tab_graph.setObjectName("tab_graph") + self.gridLayout_13 = QtWidgets.QGridLayout(self.tab_graph) + self.gridLayout_13.setObjectName("gridLayout_13") + self.widget = QtWidgets.QWidget(self.tab_graph) + self.widget.setObjectName("widget") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_3.setHorizontalSpacing(10) + self.gridLayout_3.setObjectName("gridLayout_3") + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_3.addItem(spacerItem, 6, 0, 1, 1) + self.spinBox_image_number = QtWidgets.QSpinBox(self.widget) + self.spinBox_image_number.setObjectName("spinBox_image_number") + self.gridLayout_3.addWidget(self.spinBox_image_number, 0, 1, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.widget) + self.groupBox.setObjectName("groupBox") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_4.setObjectName("gridLayout_4") + self.listWidget_element_information = QtWidgets.QListWidget(self.groupBox) + self.listWidget_element_information.setObjectName("listWidget_element_information") + self.gridLayout_4.addWidget(self.listWidget_element_information, 7, 0, 1, 1) + self.pushButton_remove_element = QtWidgets.QPushButton(self.groupBox) + self.pushButton_remove_element.setObjectName("pushButton_remove_element") + self.gridLayout_4.addWidget(self.pushButton_remove_element, 9, 0, 1, 1) + self.widget_add_edge = QtWidgets.QWidget(self.groupBox) + self.widget_add_edge.setObjectName("widget_add_edge") + self.gridLayout_5 = QtWidgets.QGridLayout(self.widget_add_edge) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_3 = QtWidgets.QLabel(self.widget_add_edge) + self.label_3.setObjectName("label_3") + self.gridLayout_5.addWidget(self.label_3, 0, 0, 1, 1) + self.spinBox_edge_node = QtWidgets.QSpinBox(self.widget_add_edge) + self.spinBox_edge_node.setObjectName("spinBox_edge_node") + self.gridLayout_5.addWidget(self.spinBox_edge_node, 0, 1, 1, 1) + self.pushButton_add_edge = QtWidgets.QPushButton(self.widget_add_edge) + self.pushButton_add_edge.setObjectName("pushButton_add_edge") + self.gridLayout_5.addWidget(self.pushButton_add_edge, 0, 2, 1, 1) + self.gridLayout_4.addWidget(self.widget_add_edge, 10, 0, 1, 1) + self.label_element_information = QtWidgets.QLabel(self.groupBox) + self.label_element_information.setObjectName("label_element_information") + self.gridLayout_4.addWidget(self.label_element_information, 6, 0, 1, 1) + self.radioButton_add_edges_by_nodes = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_add_edges_by_nodes.setObjectName("radioButton_add_edges_by_nodes") + self.gridLayout_4.addWidget(self.radioButton_add_edges_by_nodes, 1, 0, 1, 1) + self.radioButton_add_nodes = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_add_nodes.setObjectName("radioButton_add_nodes") + self.gridLayout_4.addWidget(self.radioButton_add_nodes, 2, 0, 1, 1) + self.radioButton_select_graph = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_select_graph.setObjectName("radioButton_select_graph") + self.gridLayout_4.addWidget(self.radioButton_select_graph, 3, 0, 1, 1) + self.radioButton_select_none = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_select_none.setObjectName("radioButton_select_none") + self.gridLayout_4.addWidget(self.radioButton_select_none, 5, 0, 1, 1) + self.radioButton_select_network = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_select_network.setObjectName("radioButton_select_network") + self.gridLayout_4.addWidget(self.radioButton_select_network, 4, 0, 1, 1) + self.widget_picked_key_render_window = QtWidgets.QWidget(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_picked_key_render_window.sizePolicy().hasHeightForWidth()) + self.widget_picked_key_render_window.setSizePolicy(sizePolicy) + self.widget_picked_key_render_window.setMinimumSize(QtCore.QSize(0, 180)) + self.widget_picked_key_render_window.setObjectName("widget_picked_key_render_window") + self.gridLayout_4.addWidget(self.widget_picked_key_render_window, 8, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox, 3, 0, 1, 2) + self.groupBox_3 = QtWidgets.QGroupBox(self.widget) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_8 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_8.setObjectName("gridLayout_8") + self.pushButton_export_as_image = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_as_image.setObjectName("pushButton_export_as_image") + self.gridLayout_8.addWidget(self.pushButton_export_as_image, 2, 0, 1, 1) + self.widget_3 = QtWidgets.QWidget(self.groupBox_3) + self.widget_3.setObjectName("widget_3") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_3) + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_9 = QtWidgets.QLabel(self.widget_3) + self.label_9.setObjectName("label_9") + self.horizontalLayout.addWidget(self.label_9) + self.spinBox_resolution_x = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_resolution_x.setMaximum(100000) + self.spinBox_resolution_x.setProperty("value", 500) + self.spinBox_resolution_x.setObjectName("spinBox_resolution_x") + self.horizontalLayout.addWidget(self.spinBox_resolution_x) + self.label_10 = QtWidgets.QLabel(self.widget_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) + self.label_10.setSizePolicy(sizePolicy) + self.label_10.setObjectName("label_10") + self.horizontalLayout.addWidget(self.label_10) + self.spinBox_resolution_y = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_resolution_y.setMaximum(100000) + self.spinBox_resolution_y.setProperty("value", 500) + self.spinBox_resolution_y.setObjectName("spinBox_resolution_y") + self.horizontalLayout.addWidget(self.spinBox_resolution_y) + self.gridLayout_8.addWidget(self.widget_3, 0, 0, 1, 1) + self.widget_4 = QtWidgets.QWidget(self.groupBox_3) + self.widget_4.setObjectName("widget_4") + self.gridLayout_9 = QtWidgets.QGridLayout(self.widget_4) + self.gridLayout_9.setObjectName("gridLayout_9") + self.label_11 = QtWidgets.QLabel(self.widget_4) + self.label_11.setObjectName("label_11") + self.gridLayout_9.addWidget(self.label_11, 0, 0, 1, 1) + self.lineEdit_file_path = QtWidgets.QLineEdit(self.widget_4) + self.lineEdit_file_path.setObjectName("lineEdit_file_path") + self.gridLayout_9.addWidget(self.lineEdit_file_path, 0, 1, 1, 1) + self.pushButton_browse_file_system = QtWidgets.QPushButton(self.widget_4) + self.pushButton_browse_file_system.setObjectName("pushButton_browse_file_system") + self.gridLayout_9.addWidget(self.pushButton_browse_file_system, 0, 2, 1, 1) + self.gridLayout_8.addWidget(self.widget_4, 1, 0, 1, 1) + self.pushButton_export_as_csv = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_as_csv.setObjectName("pushButton_export_as_csv") + self.gridLayout_8.addWidget(self.pushButton_export_as_csv, 3, 0, 1, 1) + self.pushButton_export_rag = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_rag.setObjectName("pushButton_export_rag") + self.gridLayout_8.addWidget(self.pushButton_export_rag, 4, 0, 1, 1) + self.pushButton_export_rag_properties = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_rag_properties.setObjectName("pushButton_export_rag_properties") + self.gridLayout_8.addWidget(self.pushButton_export_rag_properties, 6, 0, 1, 1) + self.pushButton_export_as_xyz = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_as_xyz.setObjectName("pushButton_export_as_xyz") + self.gridLayout_8.addWidget(self.pushButton_export_as_xyz, 7, 0, 1, 1) + self.pushButton_export_chosen_network = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_export_chosen_network.setObjectName("pushButton_export_chosen_network") + self.gridLayout_8.addWidget(self.pushButton_export_chosen_network, 5, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_3, 5, 0, 1, 2) + self.groupBox_5 = QtWidgets.QGroupBox(self.widget) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_11.setObjectName("gridLayout_11") + self.groupBox_2 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_6.setObjectName("gridLayout_6") + self.label_4 = QtWidgets.QLabel(self.groupBox_2) + self.label_4.setObjectName("label_4") + self.gridLayout_6.addWidget(self.label_4, 7, 0, 1, 1) + self.comboBox_rag_color_map = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_rag_color_map.setObjectName("comboBox_rag_color_map") + self.gridLayout_6.addWidget(self.comboBox_rag_color_map, 7, 1, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.groupBox_2) + self.widget_2.setObjectName("widget_2") + self.gridLayout_7 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_7.setObjectName("gridLayout_7") + self.horizontalSlider_label_image_opacity = QtWidgets.QSlider(self.widget_2) + self.horizontalSlider_label_image_opacity.setMaximum(100) + self.horizontalSlider_label_image_opacity.setProperty("value", 100) + self.horizontalSlider_label_image_opacity.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_label_image_opacity.setObjectName("horizontalSlider_label_image_opacity") + self.gridLayout_7.addWidget(self.horizontalSlider_label_image_opacity, 2, 1, 1, 1) + self.spinBox_label_image_opacity = QtWidgets.QSpinBox(self.widget_2) + self.spinBox_label_image_opacity.setMaximum(100) + self.spinBox_label_image_opacity.setProperty("value", 100) + self.spinBox_label_image_opacity.setObjectName("spinBox_label_image_opacity") + self.gridLayout_7.addWidget(self.spinBox_label_image_opacity, 2, 0, 1, 1) + self.gridLayout_6.addWidget(self.widget_2, 13, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setObjectName("label_2") + self.gridLayout_6.addWidget(self.label_2, 6, 0, 1, 1) + self.doubleSpinBox_edge_radius = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.doubleSpinBox_edge_radius.setMaximum(5000.9) + self.doubleSpinBox_edge_radius.setSingleStep(0.1) + self.doubleSpinBox_edge_radius.setProperty("value", 1.0) + self.doubleSpinBox_edge_radius.setObjectName("doubleSpinBox_edge_radius") + self.gridLayout_6.addWidget(self.doubleSpinBox_edge_radius, 1, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setObjectName("label_6") + self.gridLayout_6.addWidget(self.label_6, 1, 0, 1, 1) + self.comboBox_node_color_map = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_node_color_map.setObjectName("comboBox_node_color_map") + self.gridLayout_6.addWidget(self.comboBox_node_color_map, 4, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_2) + self.label_7.setObjectName("label_7") + self.gridLayout_6.addWidget(self.label_7, 3, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_2) + self.label_8.setObjectName("label_8") + self.gridLayout_6.addWidget(self.label_8, 4, 0, 1, 1) + self.doubleSpinBox_node_radius = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.doubleSpinBox_node_radius.setMaximum(5000.9) + self.doubleSpinBox_node_radius.setSingleStep(0.1) + self.doubleSpinBox_node_radius.setProperty("value", 1.0) + self.doubleSpinBox_node_radius.setObjectName("doubleSpinBox_node_radius") + self.gridLayout_6.addWidget(self.doubleSpinBox_node_radius, 3, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.groupBox_2) + self.label_5.setObjectName("label_5") + self.gridLayout_6.addWidget(self.label_5, 12, 0, 1, 1) + self.label_17 = QtWidgets.QLabel(self.groupBox_2) + self.label_17.setObjectName("label_17") + self.gridLayout_6.addWidget(self.label_17, 0, 0, 1, 1) + self.checkBox_show_ring_graph = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_show_ring_graph.setText("") + self.checkBox_show_ring_graph.setObjectName("checkBox_show_ring_graph") + self.gridLayout_6.addWidget(self.checkBox_show_ring_graph, 0, 1, 1, 1) + self.pushButton_force_recreate = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_force_recreate.setObjectName("pushButton_force_recreate") + self.gridLayout_6.addWidget(self.pushButton_force_recreate, 16, 0, 1, 2) + self.pushButton_rag_edge_color = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_rag_edge_color.setObjectName("pushButton_rag_edge_color") + self.gridLayout_6.addWidget(self.pushButton_rag_edge_color, 6, 1, 1, 1) + self.comboBox_edge_color_selector = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_edge_color_selector.setObjectName("comboBox_edge_color_selector") + self.gridLayout_6.addWidget(self.comboBox_edge_color_selector, 5, 1, 1, 1) + self.label_23 = QtWidgets.QLabel(self.groupBox_2) + self.label_23.setObjectName("label_23") + self.gridLayout_6.addWidget(self.label_23, 5, 0, 1, 1) + self.label_24 = QtWidgets.QLabel(self.groupBox_2) + self.label_24.setObjectName("label_24") + self.gridLayout_6.addWidget(self.label_24, 14, 0, 1, 1) + self.doubleSpinBox_disk_size = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.doubleSpinBox_disk_size.setMaximum(1000000.99) + self.doubleSpinBox_disk_size.setObjectName("doubleSpinBox_disk_size") + self.gridLayout_6.addWidget(self.doubleSpinBox_disk_size, 14, 1, 1, 1) + self.pushButton_check_edges_with_image = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_check_edges_with_image.setObjectName("pushButton_check_edges_with_image") + self.gridLayout_6.addWidget(self.pushButton_check_edges_with_image, 15, 0, 1, 1) + self.checkBox_mark_incorrect_edges = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_mark_incorrect_edges.setObjectName("checkBox_mark_incorrect_edges") + self.gridLayout_6.addWidget(self.checkBox_mark_incorrect_edges, 15, 1, 1, 1) + self.gridLayout_11.addWidget(self.groupBox_2, 0, 0, 1, 1) + self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_10 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_10.setObjectName("gridLayout_10") + self.label_15 = QtWidgets.QLabel(self.groupBox_4) + self.label_15.setObjectName("label_15") + self.gridLayout_10.addWidget(self.label_15, 1, 0, 1, 1) + self.checkBox_show_network_structure = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_show_network_structure.setText("") + self.checkBox_show_network_structure.setObjectName("checkBox_show_network_structure") + self.gridLayout_10.addWidget(self.checkBox_show_network_structure, 1, 1, 1, 1) + self.label_14 = QtWidgets.QLabel(self.groupBox_4) + self.label_14.setObjectName("label_14") + self.gridLayout_10.addWidget(self.label_14, 0, 0, 1, 1) + self.checkBox_show_network_atoms = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_show_network_atoms.setText("") + self.checkBox_show_network_atoms.setObjectName("checkBox_show_network_atoms") + self.gridLayout_10.addWidget(self.checkBox_show_network_atoms, 0, 1, 1, 1) + self.doubleSpinBox_atom_2_radius = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_atom_2_radius.setMaximum(5000.9) + self.doubleSpinBox_atom_2_radius.setSingleStep(0.1) + self.doubleSpinBox_atom_2_radius.setObjectName("doubleSpinBox_atom_2_radius") + self.gridLayout_10.addWidget(self.doubleSpinBox_atom_2_radius, 5, 1, 1, 1) + self.label_atom_1_size = QtWidgets.QLabel(self.groupBox_4) + self.label_atom_1_size.setObjectName("label_atom_1_size") + self.gridLayout_10.addWidget(self.label_atom_1_size, 3, 0, 1, 1) + self.doubleSpinBox_atom_1_radius = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_atom_1_radius.setMaximum(5000.9) + self.doubleSpinBox_atom_1_radius.setSingleStep(0.1) + self.doubleSpinBox_atom_1_radius.setObjectName("doubleSpinBox_atom_1_radius") + self.gridLayout_10.addWidget(self.doubleSpinBox_atom_1_radius, 3, 1, 1, 1) + self.pushButton_atom_2_color = QtWidgets.QPushButton(self.groupBox_4) + self.pushButton_atom_2_color.setObjectName("pushButton_atom_2_color") + self.gridLayout_10.addWidget(self.pushButton_atom_2_color, 6, 1, 1, 1) + self.label_atom_2_color = QtWidgets.QLabel(self.groupBox_4) + self.label_atom_2_color.setObjectName("label_atom_2_color") + self.gridLayout_10.addWidget(self.label_atom_2_color, 6, 0, 1, 1) + self.label_atom_1_color = QtWidgets.QLabel(self.groupBox_4) + self.label_atom_1_color.setObjectName("label_atom_1_color") + self.gridLayout_10.addWidget(self.label_atom_1_color, 4, 0, 1, 1) + self.pushButton_atom_1_color = QtWidgets.QPushButton(self.groupBox_4) + self.pushButton_atom_1_color.setObjectName("pushButton_atom_1_color") + self.gridLayout_10.addWidget(self.pushButton_atom_1_color, 4, 1, 1, 1) + self.pushButton_force_recreate_atom_network = QtWidgets.QPushButton(self.groupBox_4) + self.pushButton_force_recreate_atom_network.setObjectName("pushButton_force_recreate_atom_network") + self.gridLayout_10.addWidget(self.pushButton_force_recreate_atom_network, 11, 0, 1, 2) + self.label_atom_2_size = QtWidgets.QLabel(self.groupBox_4) + self.label_atom_2_size.setObjectName("label_atom_2_size") + self.gridLayout_10.addWidget(self.label_atom_2_size, 5, 0, 1, 1) + self.doubleSpinBox_ring_thickness = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_ring_thickness.setMaximum(5000.9) + self.doubleSpinBox_ring_thickness.setSingleStep(0.1) + self.doubleSpinBox_ring_thickness.setObjectName("doubleSpinBox_ring_thickness") + self.gridLayout_10.addWidget(self.doubleSpinBox_ring_thickness, 8, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(self.groupBox_4) + self.label_16.setObjectName("label_16") + self.gridLayout_10.addWidget(self.label_16, 8, 0, 1, 1) + self.label_12 = QtWidgets.QLabel(self.groupBox_4) + self.label_12.setObjectName("label_12") + self.gridLayout_10.addWidget(self.label_12, 10, 0, 1, 1) + self.pushButton_ring_color = QtWidgets.QPushButton(self.groupBox_4) + self.pushButton_ring_color.setObjectName("pushButton_ring_color") + self.gridLayout_10.addWidget(self.pushButton_ring_color, 10, 1, 1, 1) + self.label_13 = QtWidgets.QLabel(self.groupBox_4) + self.label_13.setObjectName("label_13") + self.gridLayout_10.addWidget(self.label_13, 7, 0, 1, 1) + self.comboBox_ring_type = QtWidgets.QComboBox(self.groupBox_4) + self.comboBox_ring_type.setObjectName("comboBox_ring_type") + self.gridLayout_10.addWidget(self.comboBox_ring_type, 7, 1, 1, 1) + self.checkBox_show_network_polygons = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_show_network_polygons.setText("") + self.checkBox_show_network_polygons.setObjectName("checkBox_show_network_polygons") + self.gridLayout_10.addWidget(self.checkBox_show_network_polygons, 2, 1, 1, 1) + self.label_21 = QtWidgets.QLabel(self.groupBox_4) + self.label_21.setObjectName("label_21") + self.gridLayout_10.addWidget(self.label_21, 2, 0, 1, 1) + self.label_22 = QtWidgets.QLabel(self.groupBox_4) + self.label_22.setObjectName("label_22") + self.gridLayout_10.addWidget(self.label_22, 9, 0, 1, 1) + self.comboBox_ring_color_selector = QtWidgets.QComboBox(self.groupBox_4) + self.comboBox_ring_color_selector.setObjectName("comboBox_ring_color_selector") + self.gridLayout_10.addWidget(self.comboBox_ring_color_selector, 9, 1, 1, 1) + self.gridLayout_11.addWidget(self.groupBox_4, 1, 0, 1, 1) + self.pushButton_update_settings = QtWidgets.QPushButton(self.groupBox_5) + self.pushButton_update_settings.setObjectName("pushButton_update_settings") + self.gridLayout_11.addWidget(self.pushButton_update_settings, 2, 0, 1, 1) + self.gridLayout_3.addWidget(self.groupBox_5, 4, 0, 1, 2) + self.gridLayout_13.addWidget(self.widget, 0, 0, 1, 1) + self.tabWidget_saasmi.addTab(self.tab_graph, "") + self.tab_analysis = QtWidgets.QWidget() + self.tab_analysis.setObjectName("tab_analysis") + self.gridLayout_14 = QtWidgets.QGridLayout(self.tab_analysis) + self.gridLayout_14.setObjectName("gridLayout_14") + self.groupBox_7 = QtWidgets.QGroupBox(self.tab_analysis) + self.groupBox_7.setObjectName("groupBox_7") + self.gridLayout_17 = QtWidgets.QGridLayout(self.groupBox_7) + self.gridLayout_17.setObjectName("gridLayout_17") + self.pushButton_analyse_area = QtWidgets.QPushButton(self.groupBox_7) + self.pushButton_analyse_area.setObjectName("pushButton_analyse_area") + self.gridLayout_17.addWidget(self.pushButton_analyse_area, 0, 0, 1, 1) + self.gridLayout_14.addWidget(self.groupBox_7, 2, 0, 1, 1) + self.groupBox_8 = QtWidgets.QGroupBox(self.tab_analysis) + self.groupBox_8.setObjectName("groupBox_8") + self.gridLayout_16 = QtWidgets.QGridLayout(self.groupBox_8) + self.gridLayout_16.setObjectName("gridLayout_16") + self.textBrowser = QtWidgets.QTextBrowser(self.groupBox_8) + self.textBrowser.setObjectName("textBrowser") + self.gridLayout_16.addWidget(self.textBrowser, 0, 0, 1, 1) + self.gridLayout_14.addWidget(self.groupBox_8, 3, 0, 1, 1) + self.groupBox_6 = QtWidgets.QGroupBox(self.tab_analysis) + self.groupBox_6.setObjectName("groupBox_6") + self.gridLayout_15 = QtWidgets.QGridLayout(self.groupBox_6) + self.gridLayout_15.setObjectName("gridLayout_15") + self.label_19 = QtWidgets.QLabel(self.groupBox_6) + self.label_19.setObjectName("label_19") + self.gridLayout_15.addWidget(self.label_19, 3, 0, 1, 1) + self.widget_5 = QtWidgets.QWidget(self.groupBox_6) + self.widget_5.setObjectName("widget_5") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_5) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.radioButton_positive_area = QtWidgets.QRadioButton(self.widget_5) + self.radioButton_positive_area.setChecked(True) + self.radioButton_positive_area.setObjectName("radioButton_positive_area") + self.horizontalLayout_2.addWidget(self.radioButton_positive_area) + self.radioButton_negative_area = QtWidgets.QRadioButton(self.widget_5) + self.radioButton_negative_area.setChecked(False) + self.radioButton_negative_area.setObjectName("radioButton_negative_area") + self.horizontalLayout_2.addWidget(self.radioButton_negative_area) + self.gridLayout_15.addWidget(self.widget_5, 3, 1, 1, 1) + self.listWidget_polygon_areas = QtWidgets.QListWidget(self.groupBox_6) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget_polygon_areas.sizePolicy().hasHeightForWidth()) + self.listWidget_polygon_areas.setSizePolicy(sizePolicy) + self.listWidget_polygon_areas.setObjectName("listWidget_polygon_areas") + self.gridLayout_15.addWidget(self.listWidget_polygon_areas, 1, 0, 1, 2) + self.pushButton_remove_polygon = QtWidgets.QPushButton(self.groupBox_6) + self.pushButton_remove_polygon.setObjectName("pushButton_remove_polygon") + self.gridLayout_15.addWidget(self.pushButton_remove_polygon, 2, 0, 1, 2) + self.gridLayout_14.addWidget(self.groupBox_6, 0, 0, 1, 1) + self.groupBox_9 = QtWidgets.QGroupBox(self.tab_analysis) + self.groupBox_9.setObjectName("groupBox_9") + self.gridLayout_18 = QtWidgets.QGridLayout(self.groupBox_9) + self.gridLayout_18.setObjectName("gridLayout_18") + self.pushButton_add_image_mask = QtWidgets.QPushButton(self.groupBox_9) + self.pushButton_add_image_mask.setObjectName("pushButton_add_image_mask") + self.gridLayout_18.addWidget(self.pushButton_add_image_mask, 2, 0, 1, 1) + self.listWidget_image_masks = QtWidgets.QListWidget(self.groupBox_9) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget_image_masks.sizePolicy().hasHeightForWidth()) + self.listWidget_image_masks.setSizePolicy(sizePolicy) + self.listWidget_image_masks.setObjectName("listWidget_image_masks") + self.gridLayout_18.addWidget(self.listWidget_image_masks, 0, 0, 1, 1) + self.pushButton_remove_image_mask = QtWidgets.QPushButton(self.groupBox_9) + self.pushButton_remove_image_mask.setObjectName("pushButton_remove_image_mask") + self.gridLayout_18.addWidget(self.pushButton_remove_image_mask, 1, 0, 1, 1) + self.gridLayout_14.addWidget(self.groupBox_9, 1, 0, 1, 1) + self.tabWidget_saasmi.addTab(self.tab_analysis, "") + self.gridLayout_12.addWidget(self.tabWidget_saasmi, 4, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(self.scrollAreaWidgetContents_2) + self.label_20.setObjectName("label_20") + self.gridLayout_12.addWidget(self.label_20, 0, 0, 1, 1) + self.label_18 = QtWidgets.QLabel(self.scrollAreaWidgetContents_2) + self.label_18.setObjectName("label_18") + self.gridLayout_12.addWidget(self.label_18, 2, 0, 1, 1) + self.comboBox_visualization_backend = QtWidgets.QComboBox(self.scrollAreaWidgetContents_2) + self.comboBox_visualization_backend.setObjectName("comboBox_visualization_backend") + self.gridLayout_12.addWidget(self.comboBox_visualization_backend, 3, 0, 1, 1) + self.scrollArea.setWidget(self.scrollAreaWidgetContents_2) + self.gridLayout.addWidget(self.scrollArea, 0, 5, 1, 1) + self.widget_windows = QtWidgets.QWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_windows.sizePolicy().hasHeightForWidth()) + self.widget_windows.setSizePolicy(sizePolicy) + self.widget_windows.setObjectName("widget_windows") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_windows) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget_saasmi_graph = QtWidgets.QWidget(self.widget_windows) + self.widget_saasmi_graph.setMinimumSize(QtCore.QSize(500, 0)) + self.widget_saasmi_graph.setObjectName("widget_saasmi_graph") + self.gridLayout_2.addWidget(self.widget_saasmi_graph, 2, 0, 1, 1) + self.gridLayout.addWidget(self.widget_windows, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + self.tabWidget_saasmi.setCurrentIndex(0) + self.buttonBox.rejected.connect(Dialog.reject) + self.buttonBox.accepted.connect(Dialog.accept) + self.spinBox_label_image_opacity.valueChanged['int'].connect(self.horizontalSlider_label_image_opacity.setValue) + self.horizontalSlider_label_image_opacity.valueChanged['int'].connect(self.spinBox_label_image_opacity.setValue) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label.setText(_translate("Dialog", "Image Number:")) + self.groupBox.setTitle(_translate("Dialog", "Picked Element")) + self.pushButton_remove_element.setText(_translate("Dialog", "Remove")) + self.label_3.setText(_translate("Dialog", "Add Edge to Node")) + self.pushButton_add_edge.setText(_translate("Dialog", "Add")) + self.label_element_information.setText(_translate("Dialog", "Information")) + self.radioButton_add_edges_by_nodes.setText(_translate("Dialog", "Add Edges by picking Nodes")) + self.radioButton_add_nodes.setText(_translate("Dialog", "Add Nodes by clicking")) + self.radioButton_select_graph.setText(_translate("Dialog", "Select Graph Nodes/Edges")) + self.radioButton_select_none.setText(_translate("Dialog", "None")) + self.radioButton_select_network.setText(_translate("Dialog", "Select Network Atoms/Edges")) + self.groupBox_3.setTitle(_translate("Dialog", "Export")) + self.pushButton_export_as_image.setText(_translate("Dialog", "Export as Image")) + self.label_9.setText(_translate("Dialog", "Resolution")) + self.label_10.setText(_translate("Dialog", "x")) + self.label_11.setText(_translate("Dialog", "File Path")) + self.pushButton_browse_file_system.setText(_translate("Dialog", "Browse")) + self.pushButton_export_as_csv.setText(_translate("Dialog", "Export as csv")) + self.pushButton_export_rag.setText(_translate("Dialog", "Export RAG")) + self.pushButton_export_rag_properties.setText(_translate("Dialog", "Export Saasmi Properties")) + self.pushButton_export_as_xyz.setText(_translate("Dialog", "Export as xyz-file")) + self.pushButton_export_chosen_network.setText(_translate("Dialog", "Export chosen Network")) + self.groupBox_5.setTitle(_translate("Dialog", "Settings")) + self.groupBox_2.setTitle(_translate("Dialog", "Graph Settings")) + self.label_4.setText(_translate("Dialog", "Rag Color Map")) + self.label_2.setText(_translate("Dialog", "Edge Colors")) + self.label_6.setText(_translate("Dialog", "Edge Radius")) + self.label_7.setText(_translate("Dialog", "Node Radius")) + self.label_8.setText(_translate("Dialog", "Node Color")) + self.label_5.setText(_translate("Dialog", "LabelImage Opacity in %")) + self.label_17.setText(_translate("Dialog", "Show Ring Graph")) + self.pushButton_force_recreate.setText(_translate("Dialog", "Recreate Rag")) + self.pushButton_rag_edge_color.setText(_translate("Dialog", "Edge Colors")) + self.label_23.setText(_translate("Dialog", "Color Edges by")) + self.label_24.setText(_translate("Dialog", "Morphology Disk Size")) + self.pushButton_check_edges_with_image.setToolTip(_translate("Dialog", "

Only works in matplotlib render backend

")) + self.pushButton_check_edges_with_image.setText(_translate("Dialog", "Check Edges with Image")) + self.checkBox_mark_incorrect_edges.setToolTip(_translate("Dialog", "

Only works in matplotlib render backend

")) + self.checkBox_mark_incorrect_edges.setText(_translate("Dialog", "Mark incorrect edges")) + self.groupBox_4.setTitle(_translate("Dialog", "Network Settings")) + self.label_15.setText(_translate("Dialog", "Show Network")) + self.label_14.setText(_translate("Dialog", "Show Atoms")) + self.label_atom_1_size.setText(_translate("Dialog", "Oxygen Atom Radius")) + self.pushButton_atom_2_color.setText(_translate("Dialog", "Color")) + self.label_atom_2_color.setText(_translate("Dialog", "Silicon Atom Color")) + self.label_atom_1_color.setText(_translate("Dialog", "Oxygen Atom Color")) + self.pushButton_atom_1_color.setText(_translate("Dialog", "Color")) + self.pushButton_force_recreate_atom_network.setText(_translate("Dialog", "Recreate Atom Network")) + self.label_atom_2_size.setText(_translate("Dialog", "Silicon Atom Radius")) + self.label_16.setText(_translate("Dialog", "Ring Thickness")) + self.label_12.setText(_translate("Dialog", "Ring Color")) + self.pushButton_ring_color.setText(_translate("Dialog", "Ring Edge Color")) + self.label_13.setText(_translate("Dialog", "Ring Type")) + self.label_21.setToolTip(_translate("Dialog", "Only working with matplotlib")) + self.label_21.setText(_translate("Dialog", "Show Network Polygons")) + self.label_22.setText(_translate("Dialog", "Color Ring by ")) + self.pushButton_update_settings.setText(_translate("Dialog", "Update")) + self.tabWidget_saasmi.setTabText(self.tabWidget_saasmi.indexOf(self.tab_graph), _translate("Dialog", "Graph")) + self.groupBox_7.setTitle(_translate("Dialog", "Settings")) + self.pushButton_analyse_area.setText(_translate("Dialog", "Analyse")) + self.groupBox_8.setTitle(_translate("Dialog", "Analysis")) + self.groupBox_6.setTitle(_translate("Dialog", "Areas")) + self.label_19.setText(_translate("Dialog", "Area Type")) + self.radioButton_positive_area.setText(_translate("Dialog", "positive")) + self.radioButton_negative_area.setText(_translate("Dialog", "negative")) + self.pushButton_remove_polygon.setText(_translate("Dialog", "Remove Polygon")) + self.groupBox_9.setTitle(_translate("Dialog", "Image Masks from File")) + self.pushButton_add_image_mask.setText(_translate("Dialog", "Add Image Mask")) + self.pushButton_remove_image_mask.setText(_translate("Dialog", "Remove Image Mask")) + self.tabWidget_saasmi.setTabText(self.tabWidget_saasmi.indexOf(self.tab_analysis), _translate("Dialog", "Analysis")) + self.label_20.setText(_translate("Dialog", "Graph Selection")) + self.label_18.setText(_translate("Dialog", "Visualization Backend")) diff --git a/view/ui/ui_dialog_settings.py b/view/ui/ui_dialog_settings.py new file mode 100644 index 0000000..742b3c7 --- /dev/null +++ b/view/ui/ui_dialog_settings.py @@ -0,0 +1,337 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogSettings.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogSettings(object): + def setupUi(self, DialogSettings): + DialogSettings.setObjectName("DialogSettings") + DialogSettings.resize(548, 561) + self.verticalLayout = QtWidgets.QVBoxLayout(DialogSettings) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget_scan = QtWidgets.QTabWidget(DialogSettings) + self.tabWidget_scan.setObjectName("tabWidget_scan") + self.program = QtWidgets.QWidget() + self.program.setObjectName("program") + self.gridLayout_4 = QtWidgets.QGridLayout(self.program) + self.gridLayout_4.setObjectName("gridLayout_4") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.program) + self.groupBox.setObjectName("groupBox") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_18 = QtWidgets.QLabel(self.groupBox) + self.label_18.setObjectName("label_18") + self.gridLayout_5.addWidget(self.label_18, 3, 0, 1, 1) + self.checkBox_program_2d_multiprocessing = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_program_2d_multiprocessing.setText("") + self.checkBox_program_2d_multiprocessing.setObjectName("checkBox_program_2d_multiprocessing") + self.gridLayout_5.addWidget(self.checkBox_program_2d_multiprocessing, 2, 1, 1, 1) + self.label_17 = QtWidgets.QLabel(self.groupBox) + self.label_17.setObjectName("label_17") + self.gridLayout_5.addWidget(self.label_17, 1, 0, 1, 1) + self.label_15 = QtWidgets.QLabel(self.groupBox) + self.label_15.setObjectName("label_15") + self.gridLayout_5.addWidget(self.label_15, 0, 0, 1, 1) + self.spinBox_program_2d_concurrency = QtWidgets.QSpinBox(self.groupBox) + self.spinBox_program_2d_concurrency.setObjectName("spinBox_program_2d_concurrency") + self.gridLayout_5.addWidget(self.spinBox_program_2d_concurrency, 3, 1, 1, 1) + self.spinBox_program_1d_concurrency = QtWidgets.QSpinBox(self.groupBox) + self.spinBox_program_1d_concurrency.setObjectName("spinBox_program_1d_concurrency") + self.gridLayout_5.addWidget(self.spinBox_program_1d_concurrency, 1, 1, 1, 1) + self.checkBox_program_1d_multiprocessing = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_program_1d_multiprocessing.setText("") + self.checkBox_program_1d_multiprocessing.setObjectName("checkBox_program_1d_multiprocessing") + self.gridLayout_5.addWidget(self.checkBox_program_1d_multiprocessing, 0, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(self.groupBox) + self.label_16.setObjectName("label_16") + self.gridLayout_5.addWidget(self.label_16, 2, 0, 1, 1) + self.gridLayout_4.addWidget(self.groupBox, 1, 0, 1, 2) + self.groupBox_2 = QtWidgets.QGroupBox(self.program) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_6.setObjectName("gridLayout_6") + self.comboBox_main_render_window = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_main_render_window.setObjectName("comboBox_main_render_window") + self.comboBox_main_render_window.addItem("") + self.comboBox_main_render_window.addItem("") + self.gridLayout_6.addWidget(self.comboBox_main_render_window, 2, 4, 1, 1) + self.comboBox_1d_filter_signal = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_1d_filter_signal.setObjectName("comboBox_1d_filter_signal") + self.comboBox_1d_filter_signal.addItem("") + self.comboBox_1d_filter_signal.addItem("") + self.gridLayout_6.addWidget(self.comboBox_1d_filter_signal, 5, 4, 1, 1) + self.label_21 = QtWidgets.QLabel(self.groupBox_2) + self.label_21.setObjectName("label_21") + self.gridLayout_6.addWidget(self.label_21, 2, 0, 1, 1) + self.label_24 = QtWidgets.QLabel(self.groupBox_2) + self.label_24.setObjectName("label_24") + self.gridLayout_6.addWidget(self.label_24, 7, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(self.groupBox_2) + self.label_20.setObjectName("label_20") + self.gridLayout_6.addWidget(self.label_20, 4, 0, 1, 1) + self.label_23 = QtWidgets.QLabel(self.groupBox_2) + self.label_23.setObjectName("label_23") + self.gridLayout_6.addWidget(self.label_23, 5, 0, 1, 1) + self.label_26 = QtWidgets.QLabel(self.groupBox_2) + self.label_26.setObjectName("label_26") + self.gridLayout_6.addWidget(self.label_26, 0, 0, 1, 1) + self.checkBox_matplotlib_toolbar = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_matplotlib_toolbar.setText("") + self.checkBox_matplotlib_toolbar.setObjectName("checkBox_matplotlib_toolbar") + self.gridLayout_6.addWidget(self.checkBox_matplotlib_toolbar, 0, 4, 1, 1) + self.comboBox_second_render_window = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_second_render_window.setObjectName("comboBox_second_render_window") + self.comboBox_second_render_window.addItem("") + self.comboBox_second_render_window.addItem("") + self.gridLayout_6.addWidget(self.comboBox_second_render_window, 3, 4, 1, 1) + self.comboBox_1d_filter_image = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_1d_filter_image.setObjectName("comboBox_1d_filter_image") + self.comboBox_1d_filter_image.addItem("") + self.comboBox_1d_filter_image.addItem("") + self.gridLayout_6.addWidget(self.comboBox_1d_filter_image, 4, 4, 1, 1) + self.comboBox_2d_filter_fft = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_2d_filter_fft.setObjectName("comboBox_2d_filter_fft") + self.comboBox_2d_filter_fft.addItem("") + self.comboBox_2d_filter_fft.addItem("") + self.gridLayout_6.addWidget(self.comboBox_2d_filter_fft, 8, 4, 1, 1) + self.label_22 = QtWidgets.QLabel(self.groupBox_2) + self.label_22.setObjectName("label_22") + self.gridLayout_6.addWidget(self.label_22, 6, 0, 1, 1) + self.comboBox_2d_filter_image = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_2d_filter_image.setObjectName("comboBox_2d_filter_image") + self.comboBox_2d_filter_image.addItem("") + self.comboBox_2d_filter_image.addItem("") + self.gridLayout_6.addWidget(self.comboBox_2d_filter_image, 7, 4, 1, 1) + self.label_25 = QtWidgets.QLabel(self.groupBox_2) + self.label_25.setObjectName("label_25") + self.gridLayout_6.addWidget(self.label_25, 8, 0, 1, 1) + self.label_19 = QtWidgets.QLabel(self.groupBox_2) + self.label_19.setObjectName("label_19") + self.gridLayout_6.addWidget(self.label_19, 3, 0, 1, 1) + self.comboBox_1d_filter_fft = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox_1d_filter_fft.setObjectName("comboBox_1d_filter_fft") + self.comboBox_1d_filter_fft.addItem("") + self.comboBox_1d_filter_fft.addItem("") + self.gridLayout_6.addWidget(self.comboBox_1d_filter_fft, 6, 4, 1, 1) + self.checkBox_show_second_window = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox_show_second_window.setText("") + self.checkBox_show_second_window.setObjectName("checkBox_show_second_window") + self.gridLayout_6.addWidget(self.checkBox_show_second_window, 1, 4, 1, 1) + self.label_32 = QtWidgets.QLabel(self.groupBox_2) + self.label_32.setObjectName("label_32") + self.gridLayout_6.addWidget(self.label_32, 1, 0, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_2, 0, 0, 1, 2) + self.tabWidget_scan.addTab(self.program, "") + self.timestamps = QtWidgets.QWidget() + self.timestamps.setObjectName("timestamps") + self.gridLayout = QtWidgets.QGridLayout(self.timestamps) + self.gridLayout.setObjectName("gridLayout") + self.comboBox_timestamps_unit = QtWidgets.QComboBox(self.timestamps) + self.comboBox_timestamps_unit.setObjectName("comboBox_timestamps_unit") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.comboBox_timestamps_unit.addItem("") + self.gridLayout.addWidget(self.comboBox_timestamps_unit, 6, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.timestamps) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(self.timestamps) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 6, 0, 1, 1) + self.label = QtWidgets.QLabel(self.timestamps) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.label_10 = QtWidgets.QLabel(self.timestamps) + self.label_10.setObjectName("label_10") + self.gridLayout.addWidget(self.label_10, 10, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.timestamps) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 2, 1, 1) + self.label_9 = QtWidgets.QLabel(self.timestamps) + self.label_9.setObjectName("label_9") + self.gridLayout.addWidget(self.label_9, 7, 0, 1, 1) + self.doubleSpinBox_start_time = QtWidgets.QDoubleSpinBox(self.timestamps) + self.doubleSpinBox_start_time.setDecimals(4) + self.doubleSpinBox_start_time.setMinimum(-100000.0) + self.doubleSpinBox_start_time.setMaximum(100000.0) + self.doubleSpinBox_start_time.setObjectName("doubleSpinBox_start_time") + self.gridLayout.addWidget(self.doubleSpinBox_start_time, 5, 1, 1, 1) + self.checkBox_timestamps_show = QtWidgets.QCheckBox(self.timestamps) + self.checkBox_timestamps_show.setText("") + self.checkBox_timestamps_show.setChecked(True) + self.checkBox_timestamps_show.setObjectName("checkBox_timestamps_show") + self.gridLayout.addWidget(self.checkBox_timestamps_show, 7, 1, 1, 1) + self.widget_vtk_text_actor = QtWidgets.QWidget(self.timestamps) + self.widget_vtk_text_actor.setObjectName("widget_vtk_text_actor") + self.gridLayout.addWidget(self.widget_vtk_text_actor, 11, 0, 4, 4) + self.label_13 = QtWidgets.QLabel(self.timestamps) + self.label_13.setObjectName("label_13") + self.gridLayout.addWidget(self.label_13, 5, 0, 1, 1) + self.spinBox_timestamps_pre_decimals = QtWidgets.QSpinBox(self.timestamps) + self.spinBox_timestamps_pre_decimals.setProperty("value", 4) + self.spinBox_timestamps_pre_decimals.setObjectName("spinBox_timestamps_pre_decimals") + self.gridLayout.addWidget(self.spinBox_timestamps_pre_decimals, 0, 1, 1, 1) + self.spinBox_timestamps_position_y = QtWidgets.QSpinBox(self.timestamps) + self.spinBox_timestamps_position_y.setMaximum(10000) + self.spinBox_timestamps_position_y.setObjectName("spinBox_timestamps_position_y") + self.gridLayout.addWidget(self.spinBox_timestamps_position_y, 10, 2, 1, 1) + self.pushButton_start_time_to_current_frame = QtWidgets.QPushButton(self.timestamps) + self.pushButton_start_time_to_current_frame.setObjectName("pushButton_start_time_to_current_frame") + self.gridLayout.addWidget(self.pushButton_start_time_to_current_frame, 5, 2, 1, 1) + self.spinBox_timestamps_position_x = QtWidgets.QSpinBox(self.timestamps) + self.spinBox_timestamps_position_x.setMaximum(10000) + self.spinBox_timestamps_position_x.setObjectName("spinBox_timestamps_position_x") + self.gridLayout.addWidget(self.spinBox_timestamps_position_x, 10, 1, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem1, 14, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.timestamps) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 4, 0, 1, 1) + self.widget = QtWidgets.QWidget(self.timestamps) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 0, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 0, 3, 1, 1) + self.spinBox_timestamps_blue = QtWidgets.QSpinBox(self.widget) + self.spinBox_timestamps_blue.setMaximum(255) + self.spinBox_timestamps_blue.setObjectName("spinBox_timestamps_blue") + self.gridLayout_2.addWidget(self.spinBox_timestamps_blue, 2, 4, 1, 1) + self.label_7 = QtWidgets.QLabel(self.widget) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 0, 4, 1, 1) + self.pushButton_color_picker = QtWidgets.QPushButton(self.widget) + self.pushButton_color_picker.setObjectName("pushButton_color_picker") + self.gridLayout_2.addWidget(self.pushButton_color_picker, 2, 5, 1, 1) + self.spinBox_timestamps_green = QtWidgets.QSpinBox(self.widget) + self.spinBox_timestamps_green.setMaximum(255) + self.spinBox_timestamps_green.setObjectName("spinBox_timestamps_green") + self.gridLayout_2.addWidget(self.spinBox_timestamps_green, 2, 3, 1, 1) + self.spinBox_timestamps_red = QtWidgets.QSpinBox(self.widget) + self.spinBox_timestamps_red.setMaximum(255) + self.spinBox_timestamps_red.setObjectName("spinBox_timestamps_red") + self.gridLayout_2.addWidget(self.spinBox_timestamps_red, 2, 1, 1, 1) + self.gridLayout.addWidget(self.widget, 4, 1, 1, 3) + self.spinBox_timestamps_text_font_size = QtWidgets.QSpinBox(self.timestamps) + self.spinBox_timestamps_text_font_size.setProperty("value", 18) + self.spinBox_timestamps_text_font_size.setObjectName("spinBox_timestamps_text_font_size") + self.gridLayout.addWidget(self.spinBox_timestamps_text_font_size, 1, 1, 1, 1) + self.spinBox_timestamps_decimals = QtWidgets.QSpinBox(self.timestamps) + self.spinBox_timestamps_decimals.setProperty("value", 4) + self.spinBox_timestamps_decimals.setObjectName("spinBox_timestamps_decimals") + self.gridLayout.addWidget(self.spinBox_timestamps_decimals, 0, 3, 1, 1) + self.label_11 = QtWidgets.QLabel(self.timestamps) + self.label_11.setAlignment(QtCore.Qt.AlignCenter) + self.label_11.setObjectName("label_11") + self.gridLayout.addWidget(self.label_11, 9, 1, 1, 1) + self.label_12 = QtWidgets.QLabel(self.timestamps) + self.label_12.setAlignment(QtCore.Qt.AlignCenter) + self.label_12.setObjectName("label_12") + self.gridLayout.addWidget(self.label_12, 9, 2, 1, 1) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem2, 12, 0, 1, 1) + self.label_14 = QtWidgets.QLabel(self.timestamps) + self.label_14.setObjectName("label_14") + self.gridLayout.addWidget(self.label_14, 8, 0, 1, 1) + self.checkBox_show_scan_direction = QtWidgets.QCheckBox(self.timestamps) + self.checkBox_show_scan_direction.setText("") + self.checkBox_show_scan_direction.setChecked(True) + self.checkBox_show_scan_direction.setObjectName("checkBox_show_scan_direction") + self.gridLayout.addWidget(self.checkBox_show_scan_direction, 8, 1, 1, 1) + self.tabWidget_scan.addTab(self.timestamps, "") + self.verticalLayout.addWidget(self.tabWidget_scan) + self.buttonBox = QtWidgets.QDialogButtonBox(DialogSettings) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(DialogSettings) + self.tabWidget_scan.setCurrentIndex(1) + self.comboBox_timestamps_unit.setCurrentIndex(1) + self.buttonBox.accepted.connect(DialogSettings.accept) + self.buttonBox.rejected.connect(DialogSettings.reject) + QtCore.QMetaObject.connectSlotsByName(DialogSettings) + DialogSettings.setTabOrder(self.tabWidget_scan, self.spinBox_timestamps_pre_decimals) + DialogSettings.setTabOrder(self.spinBox_timestamps_pre_decimals, self.spinBox_timestamps_decimals) + DialogSettings.setTabOrder(self.spinBox_timestamps_decimals, self.spinBox_timestamps_text_font_size) + DialogSettings.setTabOrder(self.spinBox_timestamps_text_font_size, self.spinBox_timestamps_red) + DialogSettings.setTabOrder(self.spinBox_timestamps_red, self.spinBox_timestamps_green) + DialogSettings.setTabOrder(self.spinBox_timestamps_green, self.spinBox_timestamps_blue) + DialogSettings.setTabOrder(self.spinBox_timestamps_blue, self.pushButton_color_picker) + DialogSettings.setTabOrder(self.pushButton_color_picker, self.doubleSpinBox_start_time) + DialogSettings.setTabOrder(self.doubleSpinBox_start_time, self.comboBox_timestamps_unit) + DialogSettings.setTabOrder(self.comboBox_timestamps_unit, self.pushButton_start_time_to_current_frame) + DialogSettings.setTabOrder(self.pushButton_start_time_to_current_frame, self.checkBox_timestamps_show) + DialogSettings.setTabOrder(self.checkBox_timestamps_show, self.spinBox_timestamps_position_y) + DialogSettings.setTabOrder(self.spinBox_timestamps_position_y, self.spinBox_timestamps_position_x) + + def retranslateUi(self, DialogSettings): + _translate = QtCore.QCoreApplication.translate + DialogSettings.setWindowTitle(_translate("DialogSettings", "Settings")) + self.groupBox.setTitle(_translate("DialogSettings", "Hardware")) + self.label_18.setText(_translate("DialogSettings", "Concurrency")) + self.label_17.setText(_translate("DialogSettings", "Concurrency")) + self.label_15.setText(_translate("DialogSettings", "Multiprocessing for 1D Filter")) + self.label_16.setText(_translate("DialogSettings", "Multiprocessing for 2D Filter")) + self.groupBox_2.setTitle(_translate("DialogSettings", "Visualization")) + self.comboBox_main_render_window.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_main_render_window.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.comboBox_1d_filter_signal.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_1d_filter_signal.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.label_21.setText(_translate("DialogSettings", "Main Window")) + self.label_24.setText(_translate("DialogSettings", "Filter 2D Image")) + self.label_20.setText(_translate("DialogSettings", "Filter 1D Image Window")) + self.label_23.setText(_translate("DialogSettings", "Filter 1D Signal Window")) + self.label_26.setText(_translate("DialogSettings", "Matplotlib Toolbar")) + self.comboBox_second_render_window.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_second_render_window.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.comboBox_1d_filter_image.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_1d_filter_image.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.comboBox_2d_filter_fft.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_2d_filter_fft.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.label_22.setText(_translate("DialogSettings", "Filter 1D FFT Window")) + self.comboBox_2d_filter_image.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_2d_filter_image.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.label_25.setText(_translate("DialogSettings", "Filter 2D FFT")) + self.label_19.setText(_translate("DialogSettings", "Second Window")) + self.comboBox_1d_filter_fft.setItemText(0, _translate("DialogSettings", "VTK")) + self.comboBox_1d_filter_fft.setItemText(1, _translate("DialogSettings", "Matplotlib")) + self.label_32.setText(_translate("DialogSettings", "Show Second Render Window")) + self.tabWidget_scan.setTabText(self.tabWidget_scan.indexOf(self.program), _translate("DialogSettings", "Program")) + self.comboBox_timestamps_unit.setItemText(0, _translate("DialogSettings", "minutes")) + self.comboBox_timestamps_unit.setItemText(1, _translate("DialogSettings", "seconds")) + self.comboBox_timestamps_unit.setItemText(2, _translate("DialogSettings", "milliseconds")) + self.comboBox_timestamps_unit.setItemText(3, _translate("DialogSettings", "microseconds")) + self.label_3.setText(_translate("DialogSettings", "Font Size")) + self.label_8.setText(_translate("DialogSettings", "Unit")) + self.label.setText(_translate("DialogSettings", "Digits")) + self.label_10.setText(_translate("DialogSettings", "Text Position")) + self.label_2.setText(_translate("DialogSettings", ",")) + self.label_9.setText(_translate("DialogSettings", "Show Time Stamps")) + self.label_13.setText(_translate("DialogSettings", "Start Time")) + self.pushButton_start_time_to_current_frame.setText(_translate("DialogSettings", "Current Frames Time")) + self.label_4.setText(_translate("DialogSettings", "Color")) + self.label_5.setText(_translate("DialogSettings", "Red")) + self.label_6.setText(_translate("DialogSettings", "Green")) + self.label_7.setText(_translate("DialogSettings", "Blue")) + self.pushButton_color_picker.setText(_translate("DialogSettings", "Color Picker")) + self.label_11.setText(_translate("DialogSettings", "X")) + self.label_12.setText(_translate("DialogSettings", "Y")) + self.label_14.setText(_translate("DialogSettings", "Show Scan Direction")) + self.tabWidget_scan.setTabText(self.tabWidget_scan.indexOf(self.timestamps), _translate("DialogSettings", "Time Stamps")) diff --git a/view/ui/ui_mainwindow.py b/view/ui/ui_mainwindow.py new file mode 100644 index 0000000..efba4ef --- /dev/null +++ b/view/ui/ui_mainwindow.py @@ -0,0 +1,1681 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'main_window.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(2297, 1236) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setEnabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + self.centralwidget.setSizePolicy(sizePolicy) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout_40 = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout_40.setObjectName("gridLayout_40") + self.splitter_central_widget = QtWidgets.QSplitter(self.centralwidget) + self.splitter_central_widget.setOrientation(QtCore.Qt.Horizontal) + self.splitter_central_widget.setObjectName("splitter_central_widget") + self.layoutWidget = QtWidgets.QWidget(self.splitter_central_widget) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) + self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.widget_7 = QtWidgets.QWidget(self.layoutWidget) + self.widget_7.setObjectName("widget_7") + self.gridLayout_32 = QtWidgets.QGridLayout(self.widget_7) + self.gridLayout_32.setObjectName("gridLayout_32") + self.splitter = QtWidgets.QSplitter(self.widget_7) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.splitter_render_windows = QtWidgets.QSplitter(self.splitter) + self.splitter_render_windows.setFrameShape(QtWidgets.QFrame.NoFrame) + self.splitter_render_windows.setOrientation(QtCore.Qt.Horizontal) + self.splitter_render_windows.setObjectName("splitter_render_windows") + self.widget_first_window = QtWidgets.QWidget(self.splitter_render_windows) + self.widget_first_window.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_first_window.sizePolicy().hasHeightForWidth()) + self.widget_first_window.setSizePolicy(sizePolicy) + self.widget_first_window.setObjectName("widget_first_window") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.widget_first_window) + self.verticalLayout_5.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.splitter_render_window_1 = QtWidgets.QSplitter(self.widget_first_window) + self.splitter_render_window_1.setOrientation(QtCore.Qt.Vertical) + self.splitter_render_window_1.setObjectName("splitter_render_window_1") + self.widget_vtk_main_window = QtWidgets.QWidget(self.splitter_render_window_1) + self.widget_vtk_main_window.setMinimumSize(QtCore.QSize(0, 0)) + self.widget_vtk_main_window.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self.widget_vtk_main_window.setBaseSize(QtCore.QSize(0, 0)) + self.widget_vtk_main_window.setObjectName("widget_vtk_main_window") + self.widget_window_1_settings = QtWidgets.QWidget(self.splitter_render_window_1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_window_1_settings.sizePolicy().hasHeightForWidth()) + self.widget_window_1_settings.setSizePolicy(sizePolicy) + self.widget_window_1_settings.setMinimumSize(QtCore.QSize(0, 0)) + self.widget_window_1_settings.setObjectName("widget_window_1_settings") + self.gridLayout_15 = QtWidgets.QGridLayout(self.widget_window_1_settings) + self.gridLayout_15.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.gridLayout_15.setContentsMargins(0, 0, 15, 0) + self.gridLayout_15.setObjectName("gridLayout_15") + self.spinBox_window_1 = QtWidgets.QSpinBox(self.widget_window_1_settings) + self.spinBox_window_1.setObjectName("spinBox_window_1") + self.gridLayout_15.addWidget(self.spinBox_window_1, 1, 0, 1, 1) + self.comboBox_scan_window_1 = QtWidgets.QComboBox(self.widget_window_1_settings) + self.comboBox_scan_window_1.setObjectName("comboBox_scan_window_1") + self.gridLayout_15.addWidget(self.comboBox_scan_window_1, 0, 1, 1, 1) + self.widget_horizontal = QtWidgets.QWidget(self.widget_window_1_settings) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_horizontal.sizePolicy().hasHeightForWidth()) + self.widget_horizontal.setSizePolicy(sizePolicy) + self.widget_horizontal.setObjectName("widget_horizontal") + self.gridLayout_16 = QtWidgets.QGridLayout(self.widget_horizontal) + self.gridLayout_16.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.gridLayout_16.setObjectName("gridLayout_16") + self.widget_19 = QtWidgets.QWidget(self.widget_horizontal) + self.widget_19.setObjectName("widget_19") + self.gridLayout_51 = QtWidgets.QGridLayout(self.widget_19) + self.gridLayout_51.setObjectName("gridLayout_51") + self.label_65 = QtWidgets.QLabel(self.widget_19) + self.label_65.setObjectName("label_65") + self.gridLayout_51.addWidget(self.label_65, 0, 0, 1, 1) + self.label_cursor_position = QtWidgets.QLabel(self.widget_19) + self.label_cursor_position.setObjectName("label_cursor_position") + self.gridLayout_51.addWidget(self.label_cursor_position, 0, 1, 1, 1) + self.gridLayout_16.addWidget(self.widget_19, 0, 1, 1, 1) + self.widget_20 = QtWidgets.QWidget(self.widget_horizontal) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_20.sizePolicy().hasHeightForWidth()) + self.widget_20.setSizePolicy(sizePolicy) + self.widget_20.setObjectName("widget_20") + self.gridLayout_52 = QtWidgets.QGridLayout(self.widget_20) + self.gridLayout_52.setObjectName("gridLayout_52") + self.pushButtonStartAnimation = QtWidgets.QPushButton(self.widget_20) + self.pushButtonStartAnimation.setEnabled(False) + self.pushButtonStartAnimation.setObjectName("pushButtonStartAnimation") + self.gridLayout_52.addWidget(self.pushButtonStartAnimation, 0, 0, 1, 1) + self.label = QtWidgets.QLabel(self.widget_20) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.gridLayout_52.addWidget(self.label, 0, 1, 1, 1) + self.doubleSpinBox_animation_fps = QtWidgets.QDoubleSpinBox(self.widget_20) + self.doubleSpinBox_animation_fps.setEnabled(False) + self.doubleSpinBox_animation_fps.setMaximum(10000000.0) + self.doubleSpinBox_animation_fps.setProperty("value", 1.0) + self.doubleSpinBox_animation_fps.setObjectName("doubleSpinBox_animation_fps") + self.gridLayout_52.addWidget(self.doubleSpinBox_animation_fps, 0, 2, 1, 1) + self.labelFps = QtWidgets.QLabel(self.widget_20) + self.labelFps.setObjectName("labelFps") + self.gridLayout_52.addWidget(self.labelFps, 0, 3, 1, 1) + self.gridLayout_16.addWidget(self.widget_20, 0, 0, 1, 1) + self.gridLayout_15.addWidget(self.widget_horizontal, 2, 0, 1, 2) + self.horizontalSlider_window_1 = QtWidgets.QSlider(self.widget_window_1_settings) + self.horizontalSlider_window_1.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_window_1.setObjectName("horizontalSlider_window_1") + self.gridLayout_15.addWidget(self.horizontalSlider_window_1, 1, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(self.widget_window_1_settings) + self.label_16.setObjectName("label_16") + self.gridLayout_15.addWidget(self.label_16, 0, 0, 1, 1) + self.verticalLayout_5.addWidget(self.splitter_render_window_1) + self.widget_second_window = QtWidgets.QWidget(self.splitter_render_windows) + self.widget_second_window.setEnabled(False) + self.widget_second_window.setObjectName("widget_second_window") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.widget_second_window) + self.verticalLayout_6.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.splitter_render_window_2 = QtWidgets.QSplitter(self.widget_second_window) + self.splitter_render_window_2.setOrientation(QtCore.Qt.Vertical) + self.splitter_render_window_2.setObjectName("splitter_render_window_2") + self.widget_vtk_second_window = QtWidgets.QWidget(self.splitter_render_window_2) + self.widget_vtk_second_window.setEnabled(False) + self.widget_vtk_second_window.setMinimumSize(QtCore.QSize(0, 0)) + self.widget_vtk_second_window.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self.widget_vtk_second_window.setBaseSize(QtCore.QSize(0, 0)) + self.widget_vtk_second_window.setObjectName("widget_vtk_second_window") + self.widget_window_2_settings = QtWidgets.QWidget(self.splitter_render_window_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_window_2_settings.sizePolicy().hasHeightForWidth()) + self.widget_window_2_settings.setSizePolicy(sizePolicy) + self.widget_window_2_settings.setObjectName("widget_window_2_settings") + self.gridLayout_14 = QtWidgets.QGridLayout(self.widget_window_2_settings) + self.gridLayout_14.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.gridLayout_14.setContentsMargins(15, 0, 9, 0) + self.gridLayout_14.setObjectName("gridLayout_14") + self.spinBox_window_2 = QtWidgets.QSpinBox(self.widget_window_2_settings) + self.spinBox_window_2.setObjectName("spinBox_window_2") + self.gridLayout_14.addWidget(self.spinBox_window_2, 1, 0, 1, 1) + self.comboBox_scan_window_2 = QtWidgets.QComboBox(self.widget_window_2_settings) + self.comboBox_scan_window_2.setObjectName("comboBox_scan_window_2") + self.gridLayout_14.addWidget(self.comboBox_scan_window_2, 0, 1, 1, 1) + self.widget_14 = QtWidgets.QWidget(self.widget_window_2_settings) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_14.sizePolicy().hasHeightForWidth()) + self.widget_14.setSizePolicy(sizePolicy) + self.widget_14.setObjectName("widget_14") + self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.widget_14) + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.checkBox_snychronise = QtWidgets.QCheckBox(self.widget_14) + self.checkBox_snychronise.setObjectName("checkBox_snychronise") + self.horizontalLayout_6.addWidget(self.checkBox_snychronise) + self.gridLayout_14.addWidget(self.widget_14, 2, 0, 1, 2) + self.label_17 = QtWidgets.QLabel(self.widget_window_2_settings) + self.label_17.setObjectName("label_17") + self.gridLayout_14.addWidget(self.label_17, 0, 0, 1, 1) + self.horizontalSlider_window_2 = QtWidgets.QSlider(self.widget_window_2_settings) + self.horizontalSlider_window_2.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_window_2.setObjectName("horizontalSlider_window_2") + self.gridLayout_14.addWidget(self.horizontalSlider_window_2, 1, 1, 1, 1) + self.verticalLayout_6.addWidget(self.splitter_render_window_2) + self.scrollArea = QtWidgets.QScrollArea(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) + self.scrollArea.setSizePolicy(sizePolicy) + self.scrollArea.setMaximumSize(QtCore.QSize(16777215, 200)) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1653, 198)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.gridLayout_35 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents) + self.gridLayout_35.setObjectName("gridLayout_35") + self.label_28 = QtWidgets.QLabel(self.scrollAreaWidgetContents) + self.label_28.setObjectName("label_28") + self.gridLayout_35.addWidget(self.label_28, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_35.addItem(spacerItem, 0, 2, 1, 1) + self.textBrowser_logs = QtWidgets.QTextBrowser(self.scrollAreaWidgetContents) + self.textBrowser_logs.setEnabled(True) + self.textBrowser_logs.setObjectName("textBrowser_logs") + self.gridLayout_35.addWidget(self.textBrowser_logs, 1, 0, 1, 3) + self.pushButton_clear_logs = QtWidgets.QPushButton(self.scrollAreaWidgetContents) + self.pushButton_clear_logs.setObjectName("pushButton_clear_logs") + self.gridLayout_35.addWidget(self.pushButton_clear_logs, 0, 1, 1, 1) + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.gridLayout_32.addWidget(self.splitter, 0, 0, 1, 1) + self.gridLayout.addWidget(self.widget_7, 0, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(self.splitter_central_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setMaximumSize(QtCore.QSize(600, 16777215)) + self.widget_2.setObjectName("widget_2") + self.verticalLayout = QtWidgets.QVBoxLayout(self.widget_2) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.scrollArea_2 = QtWidgets.QScrollArea(self.widget_2) + self.scrollArea_2.setEnabled(True) + self.scrollArea_2.setWidgetResizable(True) + self.scrollArea_2.setObjectName("scrollArea_2") + self.scrollAreaWidgetContents_2 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(-76, -72, 677, 1256)) + self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2") + self.gridLayout_10 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_2) + self.gridLayout_10.setObjectName("gridLayout_10") + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_10.addItem(spacerItem1, 2, 0, 1, 1) + self.tabWidget_controller = QtWidgets.QTabWidget(self.scrollAreaWidgetContents_2) + self.tabWidget_controller.setObjectName("tabWidget_controller") + self.tab_scan = QtWidgets.QWidget() + self.tab_scan.setObjectName("tab_scan") + self.gridLayout_25 = QtWidgets.QGridLayout(self.tab_scan) + self.gridLayout_25.setObjectName("gridLayout_25") + self.label_53 = QtWidgets.QLabel(self.tab_scan) + self.label_53.setObjectName("label_53") + self.gridLayout_25.addWidget(self.label_53, 1, 0, 1, 1) + self.textBrowser_scan_information = QtWidgets.QTextBrowser(self.tab_scan) + self.textBrowser_scan_information.setObjectName("textBrowser_scan_information") + self.gridLayout_25.addWidget(self.textBrowser_scan_information, 3, 0, 1, 1) + self.groupBox_scan_direction = QtWidgets.QGroupBox(self.tab_scan) + self.groupBox_scan_direction.setEnabled(False) + self.groupBox_scan_direction.setObjectName("groupBox_scan_direction") + self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_scan_direction) + self.gridLayout_11.setObjectName("gridLayout_11") + self.widget_current_scan = QtWidgets.QWidget(self.groupBox_scan_direction) + self.widget_current_scan.setEnabled(False) + self.widget_current_scan.setObjectName("widget_current_scan") + self.gridLayout_13 = QtWidgets.QGridLayout(self.widget_current_scan) + self.gridLayout_13.setObjectName("gridLayout_13") + self.label_15 = QtWidgets.QLabel(self.widget_current_scan) + self.label_15.setObjectName("label_15") + self.gridLayout_13.addWidget(self.label_15, 0, 0, 1, 1) + self.comboBox_set_current_scan = QtWidgets.QComboBox(self.widget_current_scan) + self.comboBox_set_current_scan.setObjectName("comboBox_set_current_scan") + self.gridLayout_13.addWidget(self.comboBox_set_current_scan, 0, 1, 1, 1) + self.pushButton_scan_delete = QtWidgets.QPushButton(self.widget_current_scan) + self.pushButton_scan_delete.setObjectName("pushButton_scan_delete") + self.gridLayout_13.addWidget(self.pushButton_scan_delete, 0, 2, 1, 1) + self.label_13 = QtWidgets.QLabel(self.widget_current_scan) + self.label_13.setObjectName("label_13") + self.gridLayout_13.addWidget(self.label_13, 1, 0, 1, 1) + self.comboBox_scan_image_class = QtWidgets.QComboBox(self.widget_current_scan) + self.comboBox_scan_image_class.setObjectName("comboBox_scan_image_class") + self.gridLayout_13.addWidget(self.comboBox_scan_image_class, 1, 1, 1, 1) + self.pushButton_delete_current_image_class = QtWidgets.QPushButton(self.widget_current_scan) + self.pushButton_delete_current_image_class.setObjectName("pushButton_delete_current_image_class") + self.gridLayout_13.addWidget(self.pushButton_delete_current_image_class, 1, 2, 1, 1) + self.gridLayout_11.addWidget(self.widget_current_scan, 0, 0, 1, 2) + self.widget_10 = QtWidgets.QWidget(self.groupBox_scan_direction) + self.widget_10.setObjectName("widget_10") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_10) + self.horizontalLayout.setObjectName("horizontalLayout") + self.checkBox_enable_autosave = QtWidgets.QCheckBox(self.widget_10) + self.checkBox_enable_autosave.setObjectName("checkBox_enable_autosave") + self.horizontalLayout.addWidget(self.checkBox_enable_autosave) + self.spinBox_autosave_in_seconds = QtWidgets.QSpinBox(self.widget_10) + self.spinBox_autosave_in_seconds.setMinimum(1) + self.spinBox_autosave_in_seconds.setMaximum(100000) + self.spinBox_autosave_in_seconds.setProperty("value", 30) + self.spinBox_autosave_in_seconds.setObjectName("spinBox_autosave_in_seconds") + self.horizontalLayout.addWidget(self.spinBox_autosave_in_seconds) + self.label_52 = QtWidgets.QLabel(self.widget_10) + self.label_52.setObjectName("label_52") + self.horizontalLayout.addWidget(self.label_52) + self.gridLayout_11.addWidget(self.widget_10, 1, 0, 1, 2) + self.pushButton_show_measuring_points = QtWidgets.QPushButton(self.groupBox_scan_direction) + self.pushButton_show_measuring_points.setObjectName("pushButton_show_measuring_points") + self.gridLayout_11.addWidget(self.pushButton_show_measuring_points, 5, 0, 1, 2) + self.pushButton_plot_h5_infos = QtWidgets.QPushButton(self.groupBox_scan_direction) + self.pushButton_plot_h5_infos.setObjectName("pushButton_plot_h5_infos") + self.gridLayout_11.addWidget(self.pushButton_plot_h5_infos, 6, 0, 1, 2) + self.pushButton_show_point_cloud = QtWidgets.QPushButton(self.groupBox_scan_direction) + self.pushButton_show_point_cloud.setObjectName("pushButton_show_point_cloud") + self.gridLayout_11.addWidget(self.pushButton_show_point_cloud, 3, 0, 1, 2) + self.pushButton_show_density_plot = QtWidgets.QPushButton(self.groupBox_scan_direction) + self.pushButton_show_density_plot.setObjectName("pushButton_show_density_plot") + self.gridLayout_11.addWidget(self.pushButton_show_density_plot, 4, 0, 1, 2) + self.gridLayout_25.addWidget(self.groupBox_scan_direction, 0, 0, 1, 1) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_25.addItem(spacerItem2, 4, 0, 1, 1) + self.widget_15 = QtWidgets.QWidget(self.tab_scan) + self.widget_15.setObjectName("widget_15") + self.gridLayout_45 = QtWidgets.QGridLayout(self.widget_15) + self.gridLayout_45.setObjectName("gridLayout_45") + self.lineEdit_step_size_x = QtWidgets.QLineEdit(self.widget_15) + self.lineEdit_step_size_x.setObjectName("lineEdit_step_size_x") + self.gridLayout_45.addWidget(self.lineEdit_step_size_x, 0, 1, 1, 1) + self.lineEdit_step_size_y = QtWidgets.QLineEdit(self.widget_15) + self.lineEdit_step_size_y.setObjectName("lineEdit_step_size_y") + self.gridLayout_45.addWidget(self.lineEdit_step_size_y, 1, 1, 1, 1) + self.pushButton_step_size_update = QtWidgets.QPushButton(self.widget_15) + self.pushButton_step_size_update.setObjectName("pushButton_step_size_update") + self.gridLayout_45.addWidget(self.pushButton_step_size_update, 2, 0, 1, 2) + self.label_56 = QtWidgets.QLabel(self.widget_15) + self.label_56.setObjectName("label_56") + self.gridLayout_45.addWidget(self.label_56, 0, 0, 1, 1) + self.label_57 = QtWidgets.QLabel(self.widget_15) + self.label_57.setObjectName("label_57") + self.gridLayout_45.addWidget(self.label_57, 1, 0, 1, 1) + self.pushButton_open_line_profile_dialog = QtWidgets.QPushButton(self.widget_15) + self.pushButton_open_line_profile_dialog.setObjectName("pushButton_open_line_profile_dialog") + self.gridLayout_45.addWidget(self.pushButton_open_line_profile_dialog, 3, 0, 1, 2) + self.gridLayout_25.addWidget(self.widget_15, 2, 0, 1, 1) + self.tabWidget_controller.addTab(self.tab_scan, "") + self.tab_filter = QtWidgets.QWidget() + self.tab_filter.setObjectName("tab_filter") + self.gridLayout_27 = QtWidgets.QGridLayout(self.tab_filter) + self.gridLayout_27.setObjectName("gridLayout_27") + self.groupBox_filter_1d = QtWidgets.QGroupBox(self.tab_filter) + self.groupBox_filter_1d.setEnabled(False) + self.groupBox_filter_1d.setObjectName("groupBox_filter_1d") + self.gridLayout_23 = QtWidgets.QGridLayout(self.groupBox_filter_1d) + self.gridLayout_23.setObjectName("gridLayout_23") + self.pushButton_open_filter_dialog_1d = QtWidgets.QPushButton(self.groupBox_filter_1d) + self.pushButton_open_filter_dialog_1d.setObjectName("pushButton_open_filter_dialog_1d") + self.gridLayout_23.addWidget(self.pushButton_open_filter_dialog_1d, 1, 0, 1, 1) + self.checkBox_filter_1d_use_visualyze_function = QtWidgets.QCheckBox(self.groupBox_filter_1d) + self.checkBox_filter_1d_use_visualyze_function.setObjectName("checkBox_filter_1d_use_visualyze_function") + self.gridLayout_23.addWidget(self.checkBox_filter_1d_use_visualyze_function, 1, 1, 1, 1) + self.listWidget_filter_1d = QtWidgets.QListWidget(self.groupBox_filter_1d) + self.listWidget_filter_1d.setObjectName("listWidget_filter_1d") + self.gridLayout_23.addWidget(self.listWidget_filter_1d, 0, 0, 1, 2) + self.gridLayout_27.addWidget(self.groupBox_filter_1d, 1, 0, 1, 1) + self.groupBox_filter_2d = QtWidgets.QGroupBox(self.tab_filter) + self.groupBox_filter_2d.setEnabled(False) + self.groupBox_filter_2d.setObjectName("groupBox_filter_2d") + self.gridLayout_24 = QtWidgets.QGridLayout(self.groupBox_filter_2d) + self.gridLayout_24.setObjectName("gridLayout_24") + self.pushButton_open_filter_dialog_2d = QtWidgets.QPushButton(self.groupBox_filter_2d) + self.pushButton_open_filter_dialog_2d.setObjectName("pushButton_open_filter_dialog_2d") + self.gridLayout_24.addWidget(self.pushButton_open_filter_dialog_2d, 1, 0, 1, 1) + self.checkBox_filter_2d_use_visualyze_function = QtWidgets.QCheckBox(self.groupBox_filter_2d) + self.checkBox_filter_2d_use_visualyze_function.setObjectName("checkBox_filter_2d_use_visualyze_function") + self.gridLayout_24.addWidget(self.checkBox_filter_2d_use_visualyze_function, 1, 1, 1, 1) + self.listWidget_filter_2d = QtWidgets.QListWidget(self.groupBox_filter_2d) + self.listWidget_filter_2d.setEnabled(False) + self.listWidget_filter_2d.setObjectName("listWidget_filter_2d") + self.gridLayout_24.addWidget(self.listWidget_filter_2d, 0, 0, 1, 2) + self.gridLayout_27.addWidget(self.groupBox_filter_2d, 2, 0, 1, 1) + spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_27.addItem(spacerItem3, 3, 0, 1, 1) + self.groupBox_1d_signal_full = QtWidgets.QGroupBox(self.tab_filter) + self.groupBox_1d_signal_full.setEnabled(False) + self.groupBox_1d_signal_full.setObjectName("groupBox_1d_signal_full") + self.gridLayout_34 = QtWidgets.QGridLayout(self.groupBox_1d_signal_full) + self.gridLayout_34.setObjectName("gridLayout_34") + self.pushButton_filter_full_1d_signal = QtWidgets.QPushButton(self.groupBox_1d_signal_full) + self.pushButton_filter_full_1d_signal.setObjectName("pushButton_filter_full_1d_signal") + self.gridLayout_34.addWidget(self.pushButton_filter_full_1d_signal, 1, 0, 1, 1) + self.label_31 = QtWidgets.QLabel(self.groupBox_1d_signal_full) + self.label_31.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_31.setObjectName("label_31") + self.gridLayout_34.addWidget(self.label_31, 1, 1, 1, 1) + self.spinBox_full_signal_maximum_number_of_points = QtWidgets.QSpinBox(self.groupBox_1d_signal_full) + self.spinBox_full_signal_maximum_number_of_points.setMaximum(1000000000) + self.spinBox_full_signal_maximum_number_of_points.setProperty("value", 20000) + self.spinBox_full_signal_maximum_number_of_points.setObjectName("spinBox_full_signal_maximum_number_of_points") + self.gridLayout_34.addWidget(self.spinBox_full_signal_maximum_number_of_points, 1, 2, 1, 1) + self.listWidget_filter_full_1d_signal = QtWidgets.QListWidget(self.groupBox_1d_signal_full) + self.listWidget_filter_full_1d_signal.setObjectName("listWidget_filter_full_1d_signal") + self.gridLayout_34.addWidget(self.listWidget_filter_full_1d_signal, 0, 0, 1, 3) + self.gridLayout_27.addWidget(self.groupBox_1d_signal_full, 0, 0, 1, 1) + self.tabWidget_controller.addTab(self.tab_filter, "") + self.tab_visualization = QtWidgets.QWidget() + self.tab_visualization.setObjectName("tab_visualization") + self.gridLayout_29 = QtWidgets.QGridLayout(self.tab_visualization) + self.gridLayout_29.setObjectName("gridLayout_29") + self.groupBox_visualization = QtWidgets.QGroupBox(self.tab_visualization) + self.groupBox_visualization.setEnabled(False) + self.groupBox_visualization.setObjectName("groupBox_visualization") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_visualization) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.pushButton_show_average_image = QtWidgets.QPushButton(self.groupBox_visualization) + self.pushButton_show_average_image.setObjectName("pushButton_show_average_image") + self.verticalLayout_4.addWidget(self.pushButton_show_average_image) + self.widget_5 = QtWidgets.QWidget(self.groupBox_visualization) + self.widget_5.setObjectName("widget_5") + self.gridLayout_8 = QtWidgets.QGridLayout(self.widget_5) + self.gridLayout_8.setObjectName("gridLayout_8") + self.checkBox_show_rag_in_image = QtWidgets.QCheckBox(self.widget_5) + self.checkBox_show_rag_in_image.setText("") + self.checkBox_show_rag_in_image.setObjectName("checkBox_show_rag_in_image") + self.gridLayout_8.addWidget(self.checkBox_show_rag_in_image, 1, 1, 1, 1) + self.label_29 = QtWidgets.QLabel(self.widget_5) + self.label_29.setObjectName("label_29") + self.gridLayout_8.addWidget(self.label_29, 1, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(self.widget_5) + self.label_8.setObjectName("label_8") + self.gridLayout_8.addWidget(self.label_8, 0, 0, 1, 1) + self.checkBox_show_original_image = QtWidgets.QCheckBox(self.widget_5) + self.checkBox_show_original_image.setText("") + self.checkBox_show_original_image.setObjectName("checkBox_show_original_image") + self.gridLayout_8.addWidget(self.checkBox_show_original_image, 0, 1, 1, 1) + self.label_30 = QtWidgets.QLabel(self.widget_5) + self.label_30.setObjectName("label_30") + self.gridLayout_8.addWidget(self.label_30, 2, 0, 1, 1) + self.checkBox_show_features = QtWidgets.QCheckBox(self.widget_5) + self.checkBox_show_features.setText("") + self.checkBox_show_features.setChecked(True) + self.checkBox_show_features.setObjectName("checkBox_show_features") + self.gridLayout_8.addWidget(self.checkBox_show_features, 2, 1, 1, 1) + self.verticalLayout_4.addWidget(self.widget_5) + self.tabWidget_visualization = QtWidgets.QTabWidget(self.groupBox_visualization) + self.tabWidget_visualization.setFocusPolicy(QtCore.Qt.NoFocus) + self.tabWidget_visualization.setObjectName("tabWidget_visualization") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.gridLayout_9 = QtWidgets.QGridLayout(self.tab) + self.gridLayout_9.setObjectName("gridLayout_9") + self.label_12 = QtWidgets.QLabel(self.tab) + self.label_12.setObjectName("label_12") + self.gridLayout_9.addWidget(self.label_12, 0, 0, 1, 1) + self.checkBox_image_invert = QtWidgets.QCheckBox(self.tab) + self.checkBox_image_invert.setText("") + self.checkBox_image_invert.setObjectName("checkBox_image_invert") + self.gridLayout_9.addWidget(self.checkBox_image_invert, 0, 1, 1, 1) + spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_9.addItem(spacerItem4, 1, 1, 1, 1) + self.tabWidget_visualization.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.gridLayout_5 = QtWidgets.QGridLayout(self.tab_2) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_7 = QtWidgets.QLabel(self.tab_2) + self.label_7.setObjectName("label_7") + self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 1) + self.comboBox_fourier_method = QtWidgets.QComboBox(self.tab_2) + self.comboBox_fourier_method.setObjectName("comboBox_fourier_method") + self.comboBox_fourier_method.addItem("") + self.comboBox_fourier_method.addItem("") + self.gridLayout_5.addWidget(self.comboBox_fourier_method, 0, 1, 1, 1) + spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_5.addItem(spacerItem5, 1, 0, 1, 1) + self.tabWidget_visualization.addTab(self.tab_2, "") + self.tab_3 = QtWidgets.QWidget() + self.tab_3.setObjectName("tab_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.tab_3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label_4 = QtWidgets.QLabel(self.tab_3) + self.label_4.setObjectName("label_4") + self.gridLayout_4.addWidget(self.label_4, 1, 0, 1, 1) + self.spinBox_error_map_compare_image_number = QtWidgets.QSpinBox(self.tab_3) + self.spinBox_error_map_compare_image_number.setObjectName("spinBox_error_map_compare_image_number") + self.gridLayout_4.addWidget(self.spinBox_error_map_compare_image_number, 1, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.tab_3) + self.label_5.setObjectName("label_5") + self.gridLayout_4.addWidget(self.label_5, 2, 0, 1, 1) + self.comboBox_compare_method = QtWidgets.QComboBox(self.tab_3) + self.comboBox_compare_method.setObjectName("comboBox_compare_method") + self.comboBox_compare_method.addItem("") + self.comboBox_compare_method.addItem("") + self.comboBox_compare_method.addItem("") + self.comboBox_compare_method.addItem("") + self.comboBox_compare_method.addItem("") + self.gridLayout_4.addWidget(self.comboBox_compare_method, 2, 1, 1, 1) + self.label_10 = QtWidgets.QLabel(self.tab_3) + self.label_10.setObjectName("label_10") + self.gridLayout_4.addWidget(self.label_10, 0, 0, 1, 1) + self.comboBox_compare_scans = QtWidgets.QComboBox(self.tab_3) + self.comboBox_compare_scans.setObjectName("comboBox_compare_scans") + self.gridLayout_4.addWidget(self.comboBox_compare_scans, 0, 1, 1, 1) + spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem6, 3, 0, 1, 1) + self.tabWidget_visualization.addTab(self.tab_3, "") + self.tab_4 = QtWidgets.QWidget() + self.tab_4.setObjectName("tab_4") + self.gridLayout_6 = QtWidgets.QGridLayout(self.tab_4) + self.gridLayout_6.setObjectName("gridLayout_6") + self.comboBox_contours = QtWidgets.QComboBox(self.tab_4) + self.comboBox_contours.setObjectName("comboBox_contours") + self.comboBox_contours.addItem("") + self.comboBox_contours.addItem("") + self.comboBox_contours.addItem("") + self.comboBox_contours.addItem("") + self.gridLayout_6.addWidget(self.comboBox_contours, 0, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.tab_4) + self.label_6.setObjectName("label_6") + self.gridLayout_6.addWidget(self.label_6, 0, 0, 1, 1) + self.widget_contours_additional_parameters = QtWidgets.QWidget(self.tab_4) + self.widget_contours_additional_parameters.setObjectName("widget_contours_additional_parameters") + self.gridLayout_7 = QtWidgets.QGridLayout(self.widget_contours_additional_parameters) + self.gridLayout_7.setObjectName("gridLayout_7") + self.spinBox_canny_sigma = QtWidgets.QSpinBox(self.widget_contours_additional_parameters) + self.spinBox_canny_sigma.setObjectName("spinBox_canny_sigma") + self.gridLayout_7.addWidget(self.spinBox_canny_sigma, 1, 1, 1, 1) + self.label_canny_sigma = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_canny_sigma.setObjectName("label_canny_sigma") + self.gridLayout_7.addWidget(self.label_canny_sigma, 1, 0, 1, 1) + self.label_blob_max_sigma = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_blob_max_sigma.setObjectName("label_blob_max_sigma") + self.gridLayout_7.addWidget(self.label_blob_max_sigma, 3, 0, 1, 1) + self.label_blob_overlap = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_blob_overlap.setObjectName("label_blob_overlap") + self.gridLayout_7.addWidget(self.label_blob_overlap, 6, 0, 1, 1) + self.label_blob_min_sigma = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_blob_min_sigma.setObjectName("label_blob_min_sigma") + self.gridLayout_7.addWidget(self.label_blob_min_sigma, 2, 0, 1, 1) + self.spinBox_blob_max_sigma = QtWidgets.QSpinBox(self.widget_contours_additional_parameters) + self.spinBox_blob_max_sigma.setProperty("value", 5) + self.spinBox_blob_max_sigma.setObjectName("spinBox_blob_max_sigma") + self.gridLayout_7.addWidget(self.spinBox_blob_max_sigma, 3, 1, 1, 1) + self.doubleSpinBox_blob_overlap = QtWidgets.QDoubleSpinBox(self.widget_contours_additional_parameters) + self.doubleSpinBox_blob_overlap.setObjectName("doubleSpinBox_blob_overlap") + self.gridLayout_7.addWidget(self.doubleSpinBox_blob_overlap, 6, 1, 1, 1) + self.spinBox_blob_num_sigma = QtWidgets.QSpinBox(self.widget_contours_additional_parameters) + self.spinBox_blob_num_sigma.setProperty("value", 10) + self.spinBox_blob_num_sigma.setObjectName("spinBox_blob_num_sigma") + self.gridLayout_7.addWidget(self.spinBox_blob_num_sigma, 4, 1, 1, 1) + self.label_blob_num_sigma = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_blob_num_sigma.setObjectName("label_blob_num_sigma") + self.gridLayout_7.addWidget(self.label_blob_num_sigma, 4, 0, 1, 1) + self.doubleSpinBox_blob_threshold = QtWidgets.QDoubleSpinBox(self.widget_contours_additional_parameters) + self.doubleSpinBox_blob_threshold.setProperty("value", 0.2) + self.doubleSpinBox_blob_threshold.setObjectName("doubleSpinBox_blob_threshold") + self.gridLayout_7.addWidget(self.doubleSpinBox_blob_threshold, 5, 1, 1, 1) + self.label_blob_threshold = QtWidgets.QLabel(self.widget_contours_additional_parameters) + self.label_blob_threshold.setObjectName("label_blob_threshold") + self.gridLayout_7.addWidget(self.label_blob_threshold, 5, 0, 1, 1) + self.spinBox_blob_min_sigma = QtWidgets.QSpinBox(self.widget_contours_additional_parameters) + self.spinBox_blob_min_sigma.setProperty("value", 3) + self.spinBox_blob_min_sigma.setObjectName("spinBox_blob_min_sigma") + self.gridLayout_7.addWidget(self.spinBox_blob_min_sigma, 2, 1, 1, 1) + spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem7, 8, 0, 1, 1) + self.pushButton_visualization_contours_apply = QtWidgets.QPushButton(self.widget_contours_additional_parameters) + self.pushButton_visualization_contours_apply.setObjectName("pushButton_visualization_contours_apply") + self.gridLayout_7.addWidget(self.pushButton_visualization_contours_apply, 7, 0, 1, 2) + self.gridLayout_6.addWidget(self.widget_contours_additional_parameters, 1, 0, 1, 2) + self.tabWidget_visualization.addTab(self.tab_4, "") + self.verticalLayout_4.addWidget(self.tabWidget_visualization) + self.widget_4 = QtWidgets.QWidget(self.groupBox_visualization) + self.widget_4.setObjectName("widget_4") + self.gridLayout_12 = QtWidgets.QGridLayout(self.widget_4) + self.gridLayout_12.setObjectName("gridLayout_12") + self.doubleSpinBox_NaN_A = QtWidgets.QDoubleSpinBox(self.widget_4) + self.doubleSpinBox_NaN_A.setDecimals(3) + self.doubleSpinBox_NaN_A.setMaximum(1.0) + self.doubleSpinBox_NaN_A.setSingleStep(0.1) + self.doubleSpinBox_NaN_A.setProperty("value", 0.0) + self.doubleSpinBox_NaN_A.setObjectName("doubleSpinBox_NaN_A") + self.gridLayout_12.addWidget(self.doubleSpinBox_NaN_A, 0, 4, 1, 1) + self.spinBox_NaN_B = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_NaN_B.setMaximum(255) + self.spinBox_NaN_B.setObjectName("spinBox_NaN_B") + self.gridLayout_12.addWidget(self.spinBox_NaN_B, 0, 3, 1, 1) + self.spinBox_NaN_G = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_NaN_G.setMaximum(255) + self.spinBox_NaN_G.setObjectName("spinBox_NaN_G") + self.gridLayout_12.addWidget(self.spinBox_NaN_G, 0, 2, 1, 1) + self.label_3 = QtWidgets.QLabel(self.widget_4) + self.label_3.setObjectName("label_3") + self.gridLayout_12.addWidget(self.label_3, 0, 0, 1, 1) + self.spinBox_NaN_R = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_NaN_R.setMaximum(255) + self.spinBox_NaN_R.setProperty("value", 0) + self.spinBox_NaN_R.setObjectName("spinBox_NaN_R") + self.gridLayout_12.addWidget(self.spinBox_NaN_R, 0, 1, 1, 1) + self.label_14 = QtWidgets.QLabel(self.widget_4) + self.label_14.setObjectName("label_14") + self.gridLayout_12.addWidget(self.label_14, 1, 0, 1, 1) + self.spinBox_background_g = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_background_g.setMaximum(255) + self.spinBox_background_g.setObjectName("spinBox_background_g") + self.gridLayout_12.addWidget(self.spinBox_background_g, 1, 1, 1, 1) + self.spinBox_background_r = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_background_r.setMaximum(255) + self.spinBox_background_r.setObjectName("spinBox_background_r") + self.gridLayout_12.addWidget(self.spinBox_background_r, 1, 2, 1, 1) + self.spinBox_background_b = QtWidgets.QSpinBox(self.widget_4) + self.spinBox_background_b.setMaximum(255) + self.spinBox_background_b.setObjectName("spinBox_background_b") + self.gridLayout_12.addWidget(self.spinBox_background_b, 1, 3, 1, 1) + self.doubleSpinBox_background_alpha = QtWidgets.QDoubleSpinBox(self.widget_4) + self.doubleSpinBox_background_alpha.setDecimals(3) + self.doubleSpinBox_background_alpha.setMaximum(1.0) + self.doubleSpinBox_background_alpha.setSingleStep(0.1) + self.doubleSpinBox_background_alpha.setObjectName("doubleSpinBox_background_alpha") + self.gridLayout_12.addWidget(self.doubleSpinBox_background_alpha, 1, 4, 1, 1) + self.verticalLayout_4.addWidget(self.widget_4) + self.widget_6 = QtWidgets.QWidget(self.groupBox_visualization) + self.widget_6.setObjectName("widget_6") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.widget_6) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.label_9 = QtWidgets.QLabel(self.widget_6) + self.label_9.setObjectName("label_9") + self.horizontalLayout_4.addWidget(self.label_9) + self.doubleSpinBox_z_scale = QtWidgets.QDoubleSpinBox(self.widget_6) + self.doubleSpinBox_z_scale.setDecimals(4) + self.doubleSpinBox_z_scale.setMaximum(100000.0) + self.doubleSpinBox_z_scale.setProperty("value", 1.0) + self.doubleSpinBox_z_scale.setObjectName("doubleSpinBox_z_scale") + self.horizontalLayout_4.addWidget(self.doubleSpinBox_z_scale) + self.verticalLayout_4.addWidget(self.widget_6) + self.groupBox_3 = QtWidgets.QGroupBox(self.groupBox_visualization) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_3.setObjectName("gridLayout_3") + self.doubleSpinBox_contrast_absolute_min = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_absolute_min.setDecimals(4) + self.doubleSpinBox_contrast_absolute_min.setMinimum(-10000000000.0) + self.doubleSpinBox_contrast_absolute_min.setMaximum(1000000000000.0) + self.doubleSpinBox_contrast_absolute_min.setSingleStep(0.1) + self.doubleSpinBox_contrast_absolute_min.setObjectName("doubleSpinBox_contrast_absolute_min") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_absolute_min, 5, 1, 1, 1) + self.doubleSpinBox_contrast_percentile_min = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_percentile_min.setDecimals(4) + self.doubleSpinBox_contrast_percentile_min.setMaximum(99.9999) + self.doubleSpinBox_contrast_percentile_min.setProperty("value", 1.0) + self.doubleSpinBox_contrast_percentile_min.setObjectName("doubleSpinBox_contrast_percentile_min") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_percentile_min, 4, 1, 1, 1) + self.doubleSpinBox_contrast_percentile_max = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_percentile_max.setDecimals(4) + self.doubleSpinBox_contrast_percentile_max.setMaximum(100.0) + self.doubleSpinBox_contrast_percentile_max.setProperty("value", 99.0) + self.doubleSpinBox_contrast_percentile_max.setObjectName("doubleSpinBox_contrast_percentile_max") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_percentile_max, 4, 2, 1, 1) + self.doubleSpinBox_contrast_absolute_max = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox_contrast_absolute_max.setDecimals(4) + self.doubleSpinBox_contrast_absolute_max.setMinimum(-10000000000.0) + self.doubleSpinBox_contrast_absolute_max.setMaximum(1000000000000.0) + self.doubleSpinBox_contrast_absolute_max.setSingleStep(0.1) + self.doubleSpinBox_contrast_absolute_max.setObjectName("doubleSpinBox_contrast_absolute_max") + self.gridLayout_3.addWidget(self.doubleSpinBox_contrast_absolute_max, 5, 2, 1, 1) + self.radioButton_contrast_percentile = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_contrast_percentile.setChecked(True) + self.radioButton_contrast_percentile.setObjectName("radioButton_contrast_percentile") + self.gridLayout_3.addWidget(self.radioButton_contrast_percentile, 4, 0, 1, 1) + self.radioButton_contrast_absolute = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_contrast_absolute.setObjectName("radioButton_contrast_absolute") + self.gridLayout_3.addWidget(self.radioButton_contrast_absolute, 5, 0, 1, 1) + self.radioButton_full_range = QtWidgets.QRadioButton(self.groupBox_3) + self.radioButton_full_range.setChecked(False) + self.radioButton_full_range.setObjectName("radioButton_full_range") + self.gridLayout_3.addWidget(self.radioButton_full_range, 3, 0, 1, 2) + self.checkBox_fourier_logarithmic = QtWidgets.QCheckBox(self.groupBox_3) + self.checkBox_fourier_logarithmic.setChecked(True) + self.checkBox_fourier_logarithmic.setObjectName("checkBox_fourier_logarithmic") + self.gridLayout_3.addWidget(self.checkBox_fourier_logarithmic, 1, 0, 1, 2) + self.pushButton_open_contrast_dialog = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_open_contrast_dialog.setObjectName("pushButton_open_contrast_dialog") + self.gridLayout_3.addWidget(self.pushButton_open_contrast_dialog, 0, 0, 1, 3) + self.verticalLayout_4.addWidget(self.groupBox_3) + self.gridLayout_29.addWidget(self.groupBox_visualization, 0, 0, 1, 1) + self.tabWidget_controller.addTab(self.tab_visualization, "") + self.tab_drift_correction = QtWidgets.QWidget() + self.tab_drift_correction.setObjectName("tab_drift_correction") + self.gridLayout_26 = QtWidgets.QGridLayout(self.tab_drift_correction) + self.gridLayout_26.setObjectName("gridLayout_26") + self.groupBox_drift_detection = QtWidgets.QGroupBox(self.tab_drift_correction) + self.groupBox_drift_detection.setEnabled(False) + self.groupBox_drift_detection.setObjectName("groupBox_drift_detection") + self.gridLayout_22 = QtWidgets.QGridLayout(self.groupBox_drift_detection) + self.gridLayout_22.setObjectName("gridLayout_22") + self.pushButton_drift_correction_by_feature = QtWidgets.QPushButton(self.groupBox_drift_detection) + self.pushButton_drift_correction_by_feature.setObjectName("pushButton_drift_correction_by_feature") + self.gridLayout_22.addWidget(self.pushButton_drift_correction_by_feature, 1, 0, 1, 1) + self.pushButton_generate_drift_feature = QtWidgets.QPushButton(self.groupBox_drift_detection) + self.pushButton_generate_drift_feature.setObjectName("pushButton_generate_drift_feature") + self.gridLayout_22.addWidget(self.pushButton_generate_drift_feature, 0, 0, 1, 1) + self.gridLayout_26.addWidget(self.groupBox_drift_detection, 0, 0, 1, 1) + spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_26.addItem(spacerItem8, 3, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(self.tab_drift_correction) + self.groupBox.setObjectName("groupBox") + self.gridLayout_31 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_31.setObjectName("gridLayout_31") + self.spinBox_ransac_reference_image_number = QtWidgets.QSpinBox(self.groupBox) + self.spinBox_ransac_reference_image_number.setObjectName("spinBox_ransac_reference_image_number") + self.gridLayout_31.addWidget(self.spinBox_ransac_reference_image_number, 0, 1, 1, 1) + self.label_27 = QtWidgets.QLabel(self.groupBox) + self.label_27.setObjectName("label_27") + self.gridLayout_31.addWidget(self.label_27, 0, 0, 1, 1) + self.pushButton_ransac_correct_drift = QtWidgets.QPushButton(self.groupBox) + self.pushButton_ransac_correct_drift.setObjectName("pushButton_ransac_correct_drift") + self.gridLayout_31.addWidget(self.pushButton_ransac_correct_drift, 1, 0, 1, 2) + self.gridLayout_26.addWidget(self.groupBox, 1, 0, 1, 1) + self.groupBox_11 = QtWidgets.QGroupBox(self.tab_drift_correction) + self.groupBox_11.setObjectName("groupBox_11") + self.gridLayout_49 = QtWidgets.QGridLayout(self.groupBox_11) + self.gridLayout_49.setObjectName("gridLayout_49") + self.pushButton_drift_correction_by_phase_cross_correlation = QtWidgets.QPushButton(self.groupBox_11) + self.pushButton_drift_correction_by_phase_cross_correlation.setObjectName("pushButton_drift_correction_by_phase_cross_correlation") + self.gridLayout_49.addWidget(self.pushButton_drift_correction_by_phase_cross_correlation, 1, 0, 1, 1) + self.widget_18 = QtWidgets.QWidget(self.groupBox_11) + self.widget_18.setObjectName("widget_18") + self.gridLayout_50 = QtWidgets.QGridLayout(self.widget_18) + self.gridLayout_50.setObjectName("gridLayout_50") + self.comboBox_phase_correlation_method_choser = QtWidgets.QComboBox(self.widget_18) + self.comboBox_phase_correlation_method_choser.setObjectName("comboBox_phase_correlation_method_choser") + self.gridLayout_50.addWidget(self.comboBox_phase_correlation_method_choser, 0, 0, 1, 2) + self.label_64 = QtWidgets.QLabel(self.widget_18) + self.label_64.setObjectName("label_64") + self.gridLayout_50.addWidget(self.label_64, 2, 0, 1, 1) + self.lineEdit_offset_list = QtWidgets.QLineEdit(self.widget_18) + self.lineEdit_offset_list.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.lineEdit_offset_list.setObjectName("lineEdit_offset_list") + self.gridLayout_50.addWidget(self.lineEdit_offset_list, 3, 1, 1, 1) + self.label_63 = QtWidgets.QLabel(self.widget_18) + self.label_63.setObjectName("label_63") + self.gridLayout_50.addWidget(self.label_63, 3, 0, 1, 1) + self.doubleSpinBox_max_drift_vector_length = QtWidgets.QDoubleSpinBox(self.widget_18) + self.doubleSpinBox_max_drift_vector_length.setDecimals(4) + self.doubleSpinBox_max_drift_vector_length.setMinimum(0.0) + self.doubleSpinBox_max_drift_vector_length.setMaximum(1000000.0) + self.doubleSpinBox_max_drift_vector_length.setProperty("value", 0.0) + self.doubleSpinBox_max_drift_vector_length.setObjectName("doubleSpinBox_max_drift_vector_length") + self.gridLayout_50.addWidget(self.doubleSpinBox_max_drift_vector_length, 2, 1, 1, 1) + self.gridLayout_49.addWidget(self.widget_18, 0, 0, 1, 1) + self.gridLayout_26.addWidget(self.groupBox_11, 2, 0, 1, 1) + self.tabWidget_controller.addTab(self.tab_drift_correction, "") + self.tab_feature = QtWidgets.QWidget() + self.tab_feature.setObjectName("tab_feature") + self.gridLayout_28 = QtWidgets.QGridLayout(self.tab_feature) + self.gridLayout_28.setObjectName("gridLayout_28") + self.tabWidget_feature_tracker = QtWidgets.QTabWidget(self.tab_feature) + self.tabWidget_feature_tracker.setEnabled(False) + self.tabWidget_feature_tracker.setObjectName("tabWidget_feature_tracker") + self.tabWidget_feature_tracker_feature = QtWidgets.QWidget() + self.tabWidget_feature_tracker_feature.setObjectName("tabWidget_feature_tracker_feature") + self.gridLayout_17 = QtWidgets.QGridLayout(self.tabWidget_feature_tracker_feature) + self.gridLayout_17.setObjectName("gridLayout_17") + self.pushButton_load_feature_tracker_list = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_load_feature_tracker_list.setObjectName("pushButton_load_feature_tracker_list") + self.gridLayout_17.addWidget(self.pushButton_load_feature_tracker_list, 3, 1, 1, 1) + self.label_18 = QtWidgets.QLabel(self.tabWidget_feature_tracker_feature) + self.label_18.setObjectName("label_18") + self.gridLayout_17.addWidget(self.label_18, 3, 0, 1, 1) + self.lineEdit_feature_class_to_add = QtWidgets.QLineEdit(self.tabWidget_feature_tracker_feature) + self.lineEdit_feature_class_to_add.setObjectName("lineEdit_feature_class_to_add") + self.gridLayout_17.addWidget(self.lineEdit_feature_class_to_add, 6, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(self.tabWidget_feature_tracker_feature) + self.label_20.setObjectName("label_20") + self.gridLayout_17.addWidget(self.label_20, 7, 0, 1, 1) + self.checkBox_feature_tracker_auto_fill = QtWidgets.QCheckBox(self.tabWidget_feature_tracker_feature) + self.checkBox_feature_tracker_auto_fill.setText("") + self.checkBox_feature_tracker_auto_fill.setChecked(False) + self.checkBox_feature_tracker_auto_fill.setObjectName("checkBox_feature_tracker_auto_fill") + self.gridLayout_17.addWidget(self.checkBox_feature_tracker_auto_fill, 7, 1, 1, 1) + self.pushButton_save_feature_tracker_list = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_save_feature_tracker_list.setObjectName("pushButton_save_feature_tracker_list") + self.gridLayout_17.addWidget(self.pushButton_save_feature_tracker_list, 3, 2, 1, 1) + self.widget_8 = QtWidgets.QWidget(self.tabWidget_feature_tracker_feature) + self.widget_8.setObjectName("widget_8") + self.gridLayout_19 = QtWidgets.QGridLayout(self.widget_8) + self.gridLayout_19.setObjectName("gridLayout_19") + self.listWidget_feature_classes = QtWidgets.QListWidget(self.widget_8) + self.listWidget_feature_classes.setMinimumSize(QtCore.QSize(0, 300)) + self.listWidget_feature_classes.setStyleSheet("") + self.listWidget_feature_classes.setObjectName("listWidget_feature_classes") + self.gridLayout_19.addWidget(self.listWidget_feature_classes, 2, 0, 1, 1) + self.pushButton_remove_feature = QtWidgets.QPushButton(self.widget_8) + self.pushButton_remove_feature.setObjectName("pushButton_remove_feature") + self.gridLayout_19.addWidget(self.pushButton_remove_feature, 4, 2, 1, 1) + self.label_22 = QtWidgets.QLabel(self.widget_8) + self.label_22.setAlignment(QtCore.Qt.AlignCenter) + self.label_22.setObjectName("label_22") + self.gridLayout_19.addWidget(self.label_22, 1, 2, 1, 1) + self.pushButton_rescale_features = QtWidgets.QPushButton(self.widget_8) + self.pushButton_rescale_features.setObjectName("pushButton_rescale_features") + self.gridLayout_19.addWidget(self.pushButton_rescale_features, 6, 0, 1, 4) + self.pushButton_feature_analyse = QtWidgets.QPushButton(self.widget_8) + self.pushButton_feature_analyse.setObjectName("pushButton_feature_analyse") + self.gridLayout_19.addWidget(self.pushButton_feature_analyse, 8, 0, 1, 4) + self.label_23 = QtWidgets.QLabel(self.widget_8) + self.label_23.setAlignment(QtCore.Qt.AlignCenter) + self.label_23.setObjectName("label_23") + self.gridLayout_19.addWidget(self.label_23, 1, 3, 1, 1) + self.pushButton_remove_feature_class = QtWidgets.QPushButton(self.widget_8) + self.pushButton_remove_feature_class.setObjectName("pushButton_remove_feature_class") + self.gridLayout_19.addWidget(self.pushButton_remove_feature_class, 4, 0, 1, 1) + self.listWidget_features = QtWidgets.QListWidget(self.widget_8) + self.listWidget_features.setObjectName("listWidget_features") + self.gridLayout_19.addWidget(self.listWidget_features, 2, 2, 1, 1) + self.pushButton_remove_feature_point = QtWidgets.QPushButton(self.widget_8) + self.pushButton_remove_feature_point.setObjectName("pushButton_remove_feature_point") + self.gridLayout_19.addWidget(self.pushButton_remove_feature_point, 4, 3, 1, 1) + self.label_25 = QtWidgets.QLabel(self.widget_8) + self.label_25.setObjectName("label_25") + self.gridLayout_19.addWidget(self.label_25, 12, 0, 1, 1) + self.widget_13 = QtWidgets.QWidget(self.widget_8) + self.widget_13.setObjectName("widget_13") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_13) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pushButton_auto_detect_features_current_image = QtWidgets.QPushButton(self.widget_13) + self.pushButton_auto_detect_features_current_image.setObjectName("pushButton_auto_detect_features_current_image") + self.horizontalLayout_2.addWidget(self.pushButton_auto_detect_features_current_image) + self.pushButton_auto_detect_feature_clear_image = QtWidgets.QPushButton(self.widget_13) + self.pushButton_auto_detect_feature_clear_image.setObjectName("pushButton_auto_detect_feature_clear_image") + self.horizontalLayout_2.addWidget(self.pushButton_auto_detect_feature_clear_image) + self.pushButton_auto_detect_features_all_images = QtWidgets.QPushButton(self.widget_13) + self.pushButton_auto_detect_features_all_images.setObjectName("pushButton_auto_detect_features_all_images") + self.horizontalLayout_2.addWidget(self.pushButton_auto_detect_features_all_images) + self.gridLayout_19.addWidget(self.widget_13, 12, 1, 1, 3) + self.label_21 = QtWidgets.QLabel(self.widget_8) + self.label_21.setAlignment(QtCore.Qt.AlignCenter) + self.label_21.setObjectName("label_21") + self.gridLayout_19.addWidget(self.label_21, 1, 0, 1, 1) + self.listWidget_feature_points = QtWidgets.QListWidget(self.widget_8) + self.listWidget_feature_points.setObjectName("listWidget_feature_points") + self.gridLayout_19.addWidget(self.listWidget_feature_points, 2, 3, 1, 1) + self.pushButton_jump_visualization = QtWidgets.QPushButton(self.widget_8) + self.pushButton_jump_visualization.setObjectName("pushButton_jump_visualization") + self.gridLayout_19.addWidget(self.pushButton_jump_visualization, 7, 0, 1, 4) + self.widget_17 = QtWidgets.QWidget(self.widget_8) + self.widget_17.setObjectName("widget_17") + self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.widget_17) + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.pushButton_feature_index_check_all = QtWidgets.QPushButton(self.widget_17) + self.pushButton_feature_index_check_all.setObjectName("pushButton_feature_index_check_all") + self.horizontalLayout_5.addWidget(self.pushButton_feature_index_check_all) + self.pushButton_feature_index_uncheck_all = QtWidgets.QPushButton(self.widget_17) + self.pushButton_feature_index_uncheck_all.setObjectName("pushButton_feature_index_uncheck_all") + self.horizontalLayout_5.addWidget(self.pushButton_feature_index_uncheck_all) + self.gridLayout_19.addWidget(self.widget_17, 3, 2, 1, 1) + self.widget_16 = QtWidgets.QWidget(self.widget_8) + self.widget_16.setObjectName("widget_16") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget_16) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.pushButton_feature_class_check_all = QtWidgets.QPushButton(self.widget_16) + self.pushButton_feature_class_check_all.setObjectName("pushButton_feature_class_check_all") + self.horizontalLayout_3.addWidget(self.pushButton_feature_class_check_all) + self.pushButton_feature_class_uncheck_all = QtWidgets.QPushButton(self.widget_16) + self.pushButton_feature_class_uncheck_all.setObjectName("pushButton_feature_class_uncheck_all") + self.horizontalLayout_3.addWidget(self.pushButton_feature_class_uncheck_all) + self.gridLayout_19.addWidget(self.widget_16, 3, 0, 1, 1) + self.pushButton_open_neighbor_information_dialog = QtWidgets.QPushButton(self.widget_8) + self.pushButton_open_neighbor_information_dialog.setObjectName("pushButton_open_neighbor_information_dialog") + self.gridLayout_19.addWidget(self.pushButton_open_neighbor_information_dialog, 5, 0, 1, 4) + self.pushButton_generate_training_data = QtWidgets.QPushButton(self.widget_8) + self.pushButton_generate_training_data.setObjectName("pushButton_generate_training_data") + self.gridLayout_19.addWidget(self.pushButton_generate_training_data, 9, 0, 1, 4) + self.pushButton_export_lines_as_csv = QtWidgets.QPushButton(self.widget_8) + self.pushButton_export_lines_as_csv.setObjectName("pushButton_export_lines_as_csv") + self.gridLayout_19.addWidget(self.pushButton_export_lines_as_csv, 10, 0, 1, 4) + self.gridLayout_17.addWidget(self.widget_8, 9, 0, 1, 3) + self.pushButton_feature_tracker_image_copy = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_feature_tracker_image_copy.setObjectName("pushButton_feature_tracker_image_copy") + self.gridLayout_17.addWidget(self.pushButton_feature_tracker_image_copy, 11, 2, 1, 1) + self.widget = QtWidgets.QWidget(self.tabWidget_feature_tracker_feature) + self.widget.setObjectName("widget") + self.gridLayout_18 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_18.setObjectName("gridLayout_18") + self.label_19 = QtWidgets.QLabel(self.widget) + self.label_19.setObjectName("label_19") + self.gridLayout_18.addWidget(self.label_19, 0, 0, 1, 1) + self.spinBox_feature_tracker_image_copy = QtWidgets.QSpinBox(self.widget) + self.spinBox_feature_tracker_image_copy.setObjectName("spinBox_feature_tracker_image_copy") + self.gridLayout_18.addWidget(self.spinBox_feature_tracker_image_copy, 0, 1, 1, 1) + self.gridLayout_17.addWidget(self.widget, 11, 0, 1, 1) + self.widget_12 = QtWidgets.QWidget(self.tabWidget_feature_tracker_feature) + self.widget_12.setObjectName("widget_12") + self.gridLayout_21 = QtWidgets.QGridLayout(self.widget_12) + self.gridLayout_21.setObjectName("gridLayout_21") + self.pushButton_copy_feature = QtWidgets.QPushButton(self.widget_12) + self.pushButton_copy_feature.setObjectName("pushButton_copy_feature") + self.gridLayout_21.addWidget(self.pushButton_copy_feature, 0, 1, 1, 1) + self.label_24 = QtWidgets.QLabel(self.widget_12) + self.label_24.setObjectName("label_24") + self.gridLayout_21.addWidget(self.label_24, 0, 0, 1, 1) + self.pushButton_force_copy_feature = QtWidgets.QPushButton(self.widget_12) + self.pushButton_force_copy_feature.setObjectName("pushButton_force_copy_feature") + self.gridLayout_21.addWidget(self.pushButton_force_copy_feature, 0, 2, 1, 1) + self.gridLayout_17.addWidget(self.widget_12, 12, 0, 1, 3) + self.pushButton_add_feature_class = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_add_feature_class.setObjectName("pushButton_add_feature_class") + self.gridLayout_17.addWidget(self.pushButton_add_feature_class, 6, 1, 1, 1) + self.pushButton_set_feature_class_color = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_set_feature_class_color.setObjectName("pushButton_set_feature_class_color") + self.gridLayout_17.addWidget(self.pushButton_set_feature_class_color, 6, 2, 1, 1) + self.label_49 = QtWidgets.QLabel(self.tabWidget_feature_tracker_feature) + self.label_49.setObjectName("label_49") + self.gridLayout_17.addWidget(self.label_49, 8, 0, 1, 1) + self.checkBox_auto_fill_selected_features = QtWidgets.QCheckBox(self.tabWidget_feature_tracker_feature) + self.checkBox_auto_fill_selected_features.setText("") + self.checkBox_auto_fill_selected_features.setObjectName("checkBox_auto_fill_selected_features") + self.gridLayout_17.addWidget(self.checkBox_auto_fill_selected_features, 8, 1, 1, 1) + self.groupBox_8 = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_feature) + self.groupBox_8.setObjectName("groupBox_8") + self.gridLayout_48 = QtWidgets.QGridLayout(self.groupBox_8) + self.gridLayout_48.setObjectName("gridLayout_48") + self.widget_11 = QtWidgets.QWidget(self.groupBox_8) + self.widget_11.setObjectName("widget_11") + self.gridLayout_20 = QtWidgets.QGridLayout(self.widget_11) + self.gridLayout_20.setObjectName("gridLayout_20") + self.label_11 = QtWidgets.QLabel(self.widget_11) + self.label_11.setObjectName("label_11") + self.gridLayout_20.addWidget(self.label_11, 1, 0, 1, 1) + self.radioButton_pick_method_point = QtWidgets.QRadioButton(self.widget_11) + self.radioButton_pick_method_point.setObjectName("radioButton_pick_method_point") + self.gridLayout_20.addWidget(self.radioButton_pick_method_point, 1, 2, 1, 1) + self.radioButton_pick_method_feature = QtWidgets.QRadioButton(self.widget_11) + self.radioButton_pick_method_feature.setObjectName("radioButton_pick_method_feature") + self.gridLayout_20.addWidget(self.radioButton_pick_method_feature, 1, 1, 1, 1) + self.radioButton_pick_method_pick = QtWidgets.QRadioButton(self.widget_11) + self.radioButton_pick_method_pick.setObjectName("radioButton_pick_method_pick") + self.gridLayout_20.addWidget(self.radioButton_pick_method_pick, 1, 3, 1, 1) + self.label_48 = QtWidgets.QLabel(self.widget_11) + self.label_48.setObjectName("label_48") + self.gridLayout_20.addWidget(self.label_48, 3, 0, 1, 1) + self.checkBox_add_to_all_images = QtWidgets.QCheckBox(self.widget_11) + self.checkBox_add_to_all_images.setText("") + self.checkBox_add_to_all_images.setObjectName("checkBox_add_to_all_images") + self.gridLayout_20.addWidget(self.checkBox_add_to_all_images, 3, 1, 1, 3) + self.spinBox_maximum_number_of_points_per_feature = QtWidgets.QSpinBox(self.widget_11) + self.spinBox_maximum_number_of_points_per_feature.setMinimum(0) + self.spinBox_maximum_number_of_points_per_feature.setMaximum(10000) + self.spinBox_maximum_number_of_points_per_feature.setProperty("value", 0) + self.spinBox_maximum_number_of_points_per_feature.setObjectName("spinBox_maximum_number_of_points_per_feature") + self.gridLayout_20.addWidget(self.spinBox_maximum_number_of_points_per_feature, 2, 1, 1, 1) + self.label_54 = QtWidgets.QLabel(self.widget_11) + self.label_54.setObjectName("label_54") + self.gridLayout_20.addWidget(self.label_54, 2, 0, 1, 1) + self.gridLayout_48.addWidget(self.widget_11, 0, 0, 1, 1) + self.gridLayout_17.addWidget(self.groupBox_8, 0, 0, 1, 2) + self.groupBox_10 = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_feature) + self.groupBox_10.setObjectName("groupBox_10") + self.gridLayout_47 = QtWidgets.QGridLayout(self.groupBox_10) + self.gridLayout_47.setObjectName("gridLayout_47") + self.label_62 = QtWidgets.QLabel(self.groupBox_10) + self.label_62.setObjectName("label_62") + self.gridLayout_47.addWidget(self.label_62, 1, 0, 1, 1) + self.label_61 = QtWidgets.QLabel(self.groupBox_10) + self.label_61.setObjectName("label_61") + self.gridLayout_47.addWidget(self.label_61, 0, 0, 1, 1) + self.checkBox_show_feature_grid = QtWidgets.QCheckBox(self.groupBox_10) + self.checkBox_show_feature_grid.setText("") + self.checkBox_show_feature_grid.setChecked(True) + self.checkBox_show_feature_grid.setObjectName("checkBox_show_feature_grid") + self.gridLayout_47.addWidget(self.checkBox_show_feature_grid, 0, 1, 1, 1) + self.checkBox_use_feature_grid_2 = QtWidgets.QCheckBox(self.groupBox_10) + self.checkBox_use_feature_grid_2.setText("") + self.checkBox_use_feature_grid_2.setChecked(False) + self.checkBox_use_feature_grid_2.setObjectName("checkBox_use_feature_grid_2") + self.gridLayout_47.addWidget(self.checkBox_use_feature_grid_2, 1, 1, 1, 1) + self.gridLayout_17.addWidget(self.groupBox_10, 2, 0, 1, 2) + self.label_66 = QtWidgets.QLabel(self.tabWidget_feature_tracker_feature) + self.label_66.setObjectName("label_66") + self.gridLayout_17.addWidget(self.label_66, 4, 0, 1, 1) + self.pushButton_load_yolo_label_folder = QtWidgets.QPushButton(self.tabWidget_feature_tracker_feature) + self.pushButton_load_yolo_label_folder.setObjectName("pushButton_load_yolo_label_folder") + self.gridLayout_17.addWidget(self.pushButton_load_yolo_label_folder, 4, 1, 1, 1) + self.tabWidget_feature_tracker.addTab(self.tabWidget_feature_tracker_feature, "") + self.tabWidget_feature_tracker_settings = QtWidgets.QWidget() + self.tabWidget_feature_tracker_settings.setObjectName("tabWidget_feature_tracker_settings") + self.gridLayout_2 = QtWidgets.QGridLayout(self.tabWidget_feature_tracker_settings) + self.gridLayout_2.setObjectName("gridLayout_2") + self.groupBox_5 = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_settings) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_36 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_36.setObjectName("gridLayout_36") + self.label_36 = QtWidgets.QLabel(self.groupBox_5) + self.label_36.setObjectName("label_36") + self.gridLayout_36.addWidget(self.label_36, 0, 0, 1, 1) + self.comboBox_feature_feature_highlight_method = QtWidgets.QComboBox(self.groupBox_5) + self.comboBox_feature_feature_highlight_method.setObjectName("comboBox_feature_feature_highlight_method") + self.gridLayout_36.addWidget(self.comboBox_feature_feature_highlight_method, 0, 1, 1, 1) + self.label_37 = QtWidgets.QLabel(self.groupBox_5) + self.label_37.setObjectName("label_37") + self.gridLayout_36.addWidget(self.label_37, 1, 0, 1, 1) + self.widget_3 = QtWidgets.QWidget(self.groupBox_5) + self.widget_3.setObjectName("widget_3") + self.gridLayout_37 = QtWidgets.QGridLayout(self.widget_3) + self.gridLayout_37.setObjectName("gridLayout_37") + self.spinBox_feature_highlight_color_g = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_feature_highlight_color_g.setMaximum(255) + self.spinBox_feature_highlight_color_g.setObjectName("spinBox_feature_highlight_color_g") + self.gridLayout_37.addWidget(self.spinBox_feature_highlight_color_g, 1, 1, 1, 1) + self.spinBox_feature_highlight_color_r = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_feature_highlight_color_r.setMaximum(255) + self.spinBox_feature_highlight_color_r.setObjectName("spinBox_feature_highlight_color_r") + self.gridLayout_37.addWidget(self.spinBox_feature_highlight_color_r, 1, 0, 1, 1) + self.spinBox_feature_highlight_color_b = QtWidgets.QSpinBox(self.widget_3) + self.spinBox_feature_highlight_color_b.setMaximum(255) + self.spinBox_feature_highlight_color_b.setObjectName("spinBox_feature_highlight_color_b") + self.gridLayout_37.addWidget(self.spinBox_feature_highlight_color_b, 1, 2, 1, 1) + self.label_38 = QtWidgets.QLabel(self.widget_3) + self.label_38.setAlignment(QtCore.Qt.AlignCenter) + self.label_38.setObjectName("label_38") + self.gridLayout_37.addWidget(self.label_38, 0, 0, 1, 1) + self.label_39 = QtWidgets.QLabel(self.widget_3) + self.label_39.setAlignment(QtCore.Qt.AlignCenter) + self.label_39.setObjectName("label_39") + self.gridLayout_37.addWidget(self.label_39, 0, 1, 1, 1) + self.label_40 = QtWidgets.QLabel(self.widget_3) + self.label_40.setAlignment(QtCore.Qt.AlignCenter) + self.label_40.setObjectName("label_40") + self.gridLayout_37.addWidget(self.label_40, 0, 2, 1, 1) + self.gridLayout_36.addWidget(self.widget_3, 1, 1, 1, 1) + self.label_41 = QtWidgets.QLabel(self.groupBox_5) + self.label_41.setObjectName("label_41") + self.gridLayout_36.addWidget(self.label_41, 2, 0, 1, 1) + self.doubleSpinBox_feature_highlight_brightness = QtWidgets.QDoubleSpinBox(self.groupBox_5) + self.doubleSpinBox_feature_highlight_brightness.setMaximum(0.6) + self.doubleSpinBox_feature_highlight_brightness.setSingleStep(0.1) + self.doubleSpinBox_feature_highlight_brightness.setObjectName("doubleSpinBox_feature_highlight_brightness") + self.gridLayout_36.addWidget(self.doubleSpinBox_feature_highlight_brightness, 2, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_5, 3, 0, 1, 1) + self.groupBox_settings_feature_detection = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_settings) + self.groupBox_settings_feature_detection.setObjectName("groupBox_settings_feature_detection") + self.gridLayout_38 = QtWidgets.QGridLayout(self.groupBox_settings_feature_detection) + self.gridLayout_38.setObjectName("gridLayout_38") + self.comboBox_feature_auto_detect_function = QtWidgets.QComboBox(self.groupBox_settings_feature_detection) + self.comboBox_feature_auto_detect_function.setObjectName("comboBox_feature_auto_detect_function") + self.gridLayout_38.addWidget(self.comboBox_feature_auto_detect_function, 0, 1, 1, 1) + self.label_43 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_43.setObjectName("label_43") + self.gridLayout_38.addWidget(self.label_43, 0, 0, 1, 1) + self.label_42 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_42.setObjectName("label_42") + self.gridLayout_38.addWidget(self.label_42, 2, 0, 1, 1) + self.label_47 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_47.setObjectName("label_47") + self.gridLayout_38.addWidget(self.label_47, 6, 0, 1, 1) + self.doubleSpinBox_feature_auto_detect_min_sigma = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_min_sigma.setObjectName("doubleSpinBox_feature_auto_detect_min_sigma") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_min_sigma, 2, 1, 1, 1) + self.doubleSpinBox_feature_auto_detect_overlap = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_overlap.setObjectName("doubleSpinBox_feature_auto_detect_overlap") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_overlap, 6, 1, 1, 1) + self.label_46 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_46.setObjectName("label_46") + self.gridLayout_38.addWidget(self.label_46, 5, 0, 1, 1) + self.spinBox_feature_auto_detect_num_sigma = QtWidgets.QSpinBox(self.groupBox_settings_feature_detection) + self.spinBox_feature_auto_detect_num_sigma.setObjectName("spinBox_feature_auto_detect_num_sigma") + self.gridLayout_38.addWidget(self.spinBox_feature_auto_detect_num_sigma, 4, 1, 1, 1) + self.doubleSpinBox_feature_auto_detect_max_sigma = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_max_sigma.setObjectName("doubleSpinBox_feature_auto_detect_max_sigma") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_max_sigma, 3, 1, 1, 1) + self.widget_auto_detect_function_inputs = QtWidgets.QWidget(self.groupBox_settings_feature_detection) + self.widget_auto_detect_function_inputs.setObjectName("widget_auto_detect_function_inputs") + self.gridLayout_38.addWidget(self.widget_auto_detect_function_inputs, 7, 0, 1, 2) + self.label_45 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_45.setObjectName("label_45") + self.gridLayout_38.addWidget(self.label_45, 4, 0, 1, 1) + self.label_44 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_44.setObjectName("label_44") + self.gridLayout_38.addWidget(self.label_44, 3, 0, 1, 1) + self.doubleSpinBox_feature_auto_detect_threshold = QtWidgets.QDoubleSpinBox(self.groupBox_settings_feature_detection) + self.doubleSpinBox_feature_auto_detect_threshold.setObjectName("doubleSpinBox_feature_auto_detect_threshold") + self.gridLayout_38.addWidget(self.doubleSpinBox_feature_auto_detect_threshold, 5, 1, 1, 1) + self.label_59 = QtWidgets.QLabel(self.groupBox_settings_feature_detection) + self.label_59.setObjectName("label_59") + self.gridLayout_38.addWidget(self.label_59, 1, 0, 1, 1) + self.checkBox_feature_auto_detect_detect_minima = QtWidgets.QCheckBox(self.groupBox_settings_feature_detection) + self.checkBox_feature_auto_detect_detect_minima.setText("") + self.checkBox_feature_auto_detect_detect_minima.setObjectName("checkBox_feature_auto_detect_detect_minima") + self.gridLayout_38.addWidget(self.checkBox_feature_auto_detect_detect_minima, 1, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_settings_feature_detection, 0, 0, 1, 1) + spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem9, 5, 0, 1, 1) + self.pushButton_save_feature_settings_as_default = QtWidgets.QPushButton(self.tabWidget_feature_tracker_settings) + self.pushButton_save_feature_settings_as_default.setObjectName("pushButton_save_feature_settings_as_default") + self.gridLayout_2.addWidget(self.pushButton_save_feature_settings_as_default, 4, 0, 1, 1) + self.groupBox_4 = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_settings) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_33 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_33.setObjectName("gridLayout_33") + self.label_32 = QtWidgets.QLabel(self.groupBox_4) + self.label_32.setObjectName("label_32") + self.gridLayout_33.addWidget(self.label_32, 0, 0, 1, 1) + self.comboBox_feature_representation = QtWidgets.QComboBox(self.groupBox_4) + self.comboBox_feature_representation.setObjectName("comboBox_feature_representation") + self.comboBox_feature_representation.addItem("") + self.comboBox_feature_representation.addItem("") + self.comboBox_feature_representation.addItem("") + self.gridLayout_33.addWidget(self.comboBox_feature_representation, 0, 1, 1, 1) + self.label_33 = QtWidgets.QLabel(self.groupBox_4) + self.label_33.setObjectName("label_33") + self.gridLayout_33.addWidget(self.label_33, 1, 0, 1, 1) + self.doubleSpinBox_feature_point_size = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_feature_point_size.setDecimals(4) + self.doubleSpinBox_feature_point_size.setMaximum(10000000000.0) + self.doubleSpinBox_feature_point_size.setObjectName("doubleSpinBox_feature_point_size") + self.gridLayout_33.addWidget(self.doubleSpinBox_feature_point_size, 1, 1, 1, 1) + self.label_34 = QtWidgets.QLabel(self.groupBox_4) + self.label_34.setObjectName("label_34") + self.gridLayout_33.addWidget(self.label_34, 2, 0, 1, 1) + self.doubleSpinBox_feature_thickness = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_feature_thickness.setObjectName("doubleSpinBox_feature_thickness") + self.gridLayout_33.addWidget(self.doubleSpinBox_feature_thickness, 2, 1, 1, 1) + self.label_35 = QtWidgets.QLabel(self.groupBox_4) + self.label_35.setObjectName("label_35") + self.gridLayout_33.addWidget(self.label_35, 3, 0, 1, 1) + self.doubleSpinBox_feature_distance = QtWidgets.QDoubleSpinBox(self.groupBox_4) + self.doubleSpinBox_feature_distance.setMaximum(10000000000.0) + self.doubleSpinBox_feature_distance.setObjectName("doubleSpinBox_feature_distance") + self.gridLayout_33.addWidget(self.doubleSpinBox_feature_distance, 3, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_4, 2, 0, 1, 1) + self.groupBox_9 = QtWidgets.QGroupBox(self.tabWidget_feature_tracker_settings) + self.groupBox_9.setObjectName("groupBox_9") + self.gridLayout_46 = QtWidgets.QGridLayout(self.groupBox_9) + self.gridLayout_46.setObjectName("gridLayout_46") + self.label_60 = QtWidgets.QLabel(self.groupBox_9) + self.label_60.setObjectName("label_60") + self.gridLayout_46.addWidget(self.label_60, 1, 0, 1, 1) + self.checkBox_use_feature_grid = QtWidgets.QCheckBox(self.groupBox_9) + self.checkBox_use_feature_grid.setText("") + self.checkBox_use_feature_grid.setObjectName("checkBox_use_feature_grid") + self.gridLayout_46.addWidget(self.checkBox_use_feature_grid, 1, 1, 1, 1) + self.pushButton_generate_feature_grid = QtWidgets.QPushButton(self.groupBox_9) + self.pushButton_generate_feature_grid.setObjectName("pushButton_generate_feature_grid") + self.gridLayout_46.addWidget(self.pushButton_generate_feature_grid, 0, 0, 1, 2) + self.pushButton_apply_feature_grid_to_feature_points = QtWidgets.QPushButton(self.groupBox_9) + self.pushButton_apply_feature_grid_to_feature_points.setObjectName("pushButton_apply_feature_grid_to_feature_points") + self.gridLayout_46.addWidget(self.pushButton_apply_feature_grid_to_feature_points, 2, 0, 1, 2) + self.gridLayout_2.addWidget(self.groupBox_9, 1, 0, 1, 1) + self.tabWidget_feature_tracker.addTab(self.tabWidget_feature_tracker_settings, "") + self.gridLayout_28.addWidget(self.tabWidget_feature_tracker, 0, 0, 1, 1) + self.tabWidget_controller.addTab(self.tab_feature, "") + self.tab_saasmi = QtWidgets.QWidget() + self.tab_saasmi.setObjectName("tab_saasmi") + self.gridLayout_30 = QtWidgets.QGridLayout(self.tab_saasmi) + self.gridLayout_30.setObjectName("gridLayout_30") + self.doubleSpinBox_saasmi_beta = QtWidgets.QDoubleSpinBox(self.tab_saasmi) + self.doubleSpinBox_saasmi_beta.setMaximum(100000.0) + self.doubleSpinBox_saasmi_beta.setProperty("value", 250.0) + self.doubleSpinBox_saasmi_beta.setObjectName("doubleSpinBox_saasmi_beta") + self.gridLayout_30.addWidget(self.doubleSpinBox_saasmi_beta, 0, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.tab_saasmi) + self.label_2.setObjectName("label_2") + self.gridLayout_30.addWidget(self.label_2, 0, 0, 1, 1) + self.pushButton_saasmi = QtWidgets.QPushButton(self.tab_saasmi) + self.pushButton_saasmi.setObjectName("pushButton_saasmi") + self.gridLayout_30.addWidget(self.pushButton_saasmi, 5, 0, 1, 2) + spacerItem10 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_30.addItem(spacerItem10, 6, 0, 1, 1) + self.label_26 = QtWidgets.QLabel(self.tab_saasmi) + self.label_26.setObjectName("label_26") + self.gridLayout_30.addWidget(self.label_26, 1, 0, 1, 1) + self.spinBox_number_of_neighbors = QtWidgets.QSpinBox(self.tab_saasmi) + self.spinBox_number_of_neighbors.setObjectName("spinBox_number_of_neighbors") + self.gridLayout_30.addWidget(self.spinBox_number_of_neighbors, 1, 1, 1, 1) + self.label_55 = QtWidgets.QLabel(self.tab_saasmi) + self.label_55.setObjectName("label_55") + self.gridLayout_30.addWidget(self.label_55, 2, 0, 1, 1) + self.checkBox_auto_generate_rag = QtWidgets.QCheckBox(self.tab_saasmi) + self.checkBox_auto_generate_rag.setText("") + self.checkBox_auto_generate_rag.setChecked(True) + self.checkBox_auto_generate_rag.setObjectName("checkBox_auto_generate_rag") + self.gridLayout_30.addWidget(self.checkBox_auto_generate_rag, 2, 1, 1, 1) + self.pushButton_saasmi_show_prepared_images = QtWidgets.QPushButton(self.tab_saasmi) + self.pushButton_saasmi_show_prepared_images.setObjectName("pushButton_saasmi_show_prepared_images") + self.gridLayout_30.addWidget(self.pushButton_saasmi_show_prepared_images, 4, 0, 1, 2) + self.label_58 = QtWidgets.QLabel(self.tab_saasmi) + self.label_58.setObjectName("label_58") + self.gridLayout_30.addWidget(self.label_58, 3, 0, 1, 1) + self.doubleSpinBox_saasmi_disk_size = QtWidgets.QDoubleSpinBox(self.tab_saasmi) + self.doubleSpinBox_saasmi_disk_size.setMaximum(1000000.0) + self.doubleSpinBox_saasmi_disk_size.setObjectName("doubleSpinBox_saasmi_disk_size") + self.gridLayout_30.addWidget(self.doubleSpinBox_saasmi_disk_size, 3, 1, 1, 1) + self.tabWidget_controller.addTab(self.tab_saasmi, "") + self.gridLayout_10.addWidget(self.tabWidget_controller, 1, 0, 1, 1) + self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2) + self.verticalLayout.addWidget(self.scrollArea_2) + self.gridLayout_40.addWidget(self.splitter_central_widget, 0, 0, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 2297, 22)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + self.menuImport_File = QtWidgets.QMenu(self.menuFile) + self.menuImport_File.setObjectName("menuImport_File") + self.menuExport = QtWidgets.QMenu(self.menuFile) + self.menuExport.setEnabled(False) + self.menuExport.setObjectName("menuExport") + self.menuTools = QtWidgets.QMenu(self.menubar) + self.menuTools.setEnabled(False) + self.menuTools.setObjectName("menuTools") + self.menuFilter = QtWidgets.QMenu(self.menubar) + self.menuFilter.setEnabled(False) + self.menuFilter.setObjectName("menuFilter") + self.menuLayout = QtWidgets.QMenu(self.menubar) + self.menuLayout.setEnabled(False) + self.menuLayout.setObjectName("menuLayout") + MainWindow.setMenuBar(self.menubar) + self.actionSave_Scan = QtWidgets.QAction(MainWindow) + self.actionSave_Scan.setEnabled(False) + self.actionSave_Scan.setObjectName("actionSave_Scan") + self.actionImport_h5 = QtWidgets.QAction(MainWindow) + self.actionImport_h5.setObjectName("actionImport_h5") + self.actionImport_vsy = QtWidgets.QAction(MainWindow) + self.actionImport_vsy.setObjectName("actionImport_vsy") + self.actionExport_as_video = QtWidgets.QAction(MainWindow) + self.actionExport_as_video.setEnabled(True) + self.actionExport_as_video.setObjectName("actionExport_as_video") + self.actionExport_as_image = QtWidgets.QAction(MainWindow) + self.actionExport_as_image.setEnabled(True) + self.actionExport_as_image.setObjectName("actionExport_as_image") + self.actionExport_as_yspar_dat_mask_dat = QtWidgets.QAction(MainWindow) + self.actionExport_as_yspar_dat_mask_dat.setObjectName("actionExport_as_yspar_dat_mask_dat") + self.actionSettings = QtWidgets.QAction(MainWindow) + self.actionSettings.setEnabled(True) + self.actionSettings.setObjectName("actionSettings") + self.actionExport_as_Scan_Object_vsy = QtWidgets.QAction(MainWindow) + self.actionExport_as_Scan_Object_vsy.setObjectName("actionExport_as_Scan_Object_vsy") + self.actionImport_filter_log = QtWidgets.QAction(MainWindow) + self.actionImport_filter_log.setObjectName("actionImport_filter_log") + self.actionImport_filter_from_log = QtWidgets.QAction(MainWindow) + self.actionImport_filter_from_log.setEnabled(True) + self.actionImport_filter_from_log.setObjectName("actionImport_filter_from_log") + self.actionImport_from_image_file = QtWidgets.QAction(MainWindow) + self.actionImport_from_image_file.setObjectName("actionImport_from_image_file") + self.actionGenerate_Log_File = QtWidgets.QAction(MainWindow) + self.actionGenerate_Log_File.setEnabled(False) + self.actionGenerate_Log_File.setObjectName("actionGenerate_Log_File") + self.actionImport_sxm = QtWidgets.QAction(MainWindow) + self.actionImport_sxm.setObjectName("actionImport_sxm") + self.actionImport_gwy = QtWidgets.QAction(MainWindow) + self.actionImport_gwy.setObjectName("actionImport_gwy") + self.actionExport_as_gwy = QtWidgets.QAction(MainWindow) + self.actionExport_as_gwy.setObjectName("actionExport_as_gwy") + self.actionExport_as_zip_file = QtWidgets.QAction(MainWindow) + self.actionExport_as_zip_file.setEnabled(True) + self.actionExport_as_zip_file.setObjectName("actionExport_as_zip_file") + self.actionImport_scan_zip = QtWidgets.QAction(MainWindow) + self.actionImport_scan_zip.setObjectName("actionImport_scan_zip") + self.actionImport_avi = QtWidgets.QAction(MainWindow) + self.actionImport_avi.setObjectName("actionImport_avi") + self.action_reset_horizontal_layout = QtWidgets.QAction(MainWindow) + self.action_reset_horizontal_layout.setEnabled(True) + self.action_reset_horizontal_layout.setObjectName("action_reset_horizontal_layout") + self.actionImport_stp = QtWidgets.QAction(MainWindow) + self.actionImport_stp.setObjectName("actionImport_stp") + self.actionImport_npy = QtWidgets.QAction(MainWindow) + self.actionImport_npy.setObjectName("actionImport_npy") + self.actionImport_arn = QtWidgets.QAction(MainWindow) + self.actionImport_arn.setObjectName("actionImport_arn") + self.menuImport_File.addAction(self.actionImport_h5) + self.menuImport_File.addAction(self.actionImport_gwy) + self.menuImport_File.addAction(self.actionImport_from_image_file) + self.menuImport_File.addAction(self.actionImport_sxm) + self.menuImport_File.addAction(self.actionImport_vsy) + self.menuImport_File.addAction(self.actionImport_scan_zip) + self.menuImport_File.addAction(self.actionImport_avi) + self.menuImport_File.addAction(self.actionImport_stp) + self.menuImport_File.addAction(self.actionImport_npy) + self.menuImport_File.addAction(self.actionImport_arn) + self.menuExport.addAction(self.actionExport_as_video) + self.menuExport.addAction(self.actionExport_as_image) + self.menuExport.addAction(self.actionExport_as_yspar_dat_mask_dat) + self.menuExport.addAction(self.actionExport_as_Scan_Object_vsy) + self.menuExport.addAction(self.actionExport_as_gwy) + self.menuExport.addAction(self.actionExport_as_zip_file) + self.menuFile.addAction(self.menuImport_File.menuAction()) + self.menuFile.addAction(self.menuExport.menuAction()) + self.menuFile.addAction(self.actionGenerate_Log_File) + self.menuTools.addAction(self.actionSettings) + self.menuFilter.addAction(self.actionImport_filter_from_log) + self.menuLayout.addAction(self.action_reset_horizontal_layout) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuFilter.menuAction()) + self.menubar.addAction(self.menuTools.menuAction()) + self.menubar.addAction(self.menuLayout.menuAction()) + + self.retranslateUi(MainWindow) + self.tabWidget_controller.setCurrentIndex(4) + self.tabWidget_visualization.setCurrentIndex(3) + self.tabWidget_feature_tracker.setCurrentIndex(0) + self.spinBox_window_2.valueChanged['int'].connect(self.horizontalSlider_window_2.setValue) + self.horizontalSlider_window_2.valueChanged['int'].connect(self.spinBox_window_2.setValue) + self.horizontalSlider_window_1.valueChanged['int'].connect(self.spinBox_window_1.setValue) + self.spinBox_window_1.valueChanged['int'].connect(self.horizontalSlider_window_1.setValue) + self.doubleSpinBox_contrast_percentile_max.valueChanged['QString'].connect(self.radioButton_contrast_percentile.click) + self.doubleSpinBox_contrast_percentile_min.valueChanged['QString'].connect(self.radioButton_contrast_percentile.click) + self.doubleSpinBox_contrast_absolute_min.valueChanged['double'].connect(self.radioButton_contrast_absolute.click) + self.doubleSpinBox_contrast_absolute_max.valueChanged['double'].connect(self.radioButton_contrast_absolute.click) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + MainWindow.setTabOrder(self.lineEdit_step_size_x, self.lineEdit_step_size_y) + MainWindow.setTabOrder(self.lineEdit_step_size_y, self.spinBox_window_1) + MainWindow.setTabOrder(self.spinBox_window_1, self.listWidget_filter_1d) + MainWindow.setTabOrder(self.listWidget_filter_1d, self.pushButton_open_filter_dialog_1d) + MainWindow.setTabOrder(self.pushButton_open_filter_dialog_1d, self.listWidget_filter_2d) + MainWindow.setTabOrder(self.listWidget_filter_2d, self.pushButton_open_filter_dialog_2d) + MainWindow.setTabOrder(self.pushButton_open_filter_dialog_2d, self.checkBox_image_invert) + MainWindow.setTabOrder(self.checkBox_image_invert, self.comboBox_fourier_method) + MainWindow.setTabOrder(self.comboBox_fourier_method, self.spinBox_error_map_compare_image_number) + MainWindow.setTabOrder(self.spinBox_error_map_compare_image_number, self.comboBox_compare_method) + MainWindow.setTabOrder(self.comboBox_compare_method, self.comboBox_compare_scans) + MainWindow.setTabOrder(self.comboBox_compare_scans, self.comboBox_contours) + MainWindow.setTabOrder(self.comboBox_contours, self.spinBox_canny_sigma) + MainWindow.setTabOrder(self.spinBox_canny_sigma, self.spinBox_blob_max_sigma) + MainWindow.setTabOrder(self.spinBox_blob_max_sigma, self.doubleSpinBox_blob_overlap) + MainWindow.setTabOrder(self.doubleSpinBox_blob_overlap, self.spinBox_blob_num_sigma) + MainWindow.setTabOrder(self.spinBox_blob_num_sigma, self.doubleSpinBox_blob_threshold) + MainWindow.setTabOrder(self.doubleSpinBox_blob_threshold, self.spinBox_blob_min_sigma) + MainWindow.setTabOrder(self.spinBox_blob_min_sigma, self.doubleSpinBox_NaN_A) + MainWindow.setTabOrder(self.doubleSpinBox_NaN_A, self.spinBox_NaN_B) + MainWindow.setTabOrder(self.spinBox_NaN_B, self.spinBox_NaN_G) + MainWindow.setTabOrder(self.spinBox_NaN_G, self.spinBox_NaN_R) + MainWindow.setTabOrder(self.spinBox_NaN_R, self.spinBox_background_g) + MainWindow.setTabOrder(self.spinBox_background_g, self.spinBox_background_r) + MainWindow.setTabOrder(self.spinBox_background_r, self.spinBox_background_b) + MainWindow.setTabOrder(self.spinBox_background_b, self.doubleSpinBox_background_alpha) + MainWindow.setTabOrder(self.doubleSpinBox_background_alpha, self.checkBox_show_original_image) + MainWindow.setTabOrder(self.checkBox_show_original_image, self.doubleSpinBox_z_scale) + MainWindow.setTabOrder(self.doubleSpinBox_z_scale, self.doubleSpinBox_contrast_absolute_min) + MainWindow.setTabOrder(self.doubleSpinBox_contrast_absolute_min, self.doubleSpinBox_contrast_percentile_min) + MainWindow.setTabOrder(self.doubleSpinBox_contrast_percentile_min, self.doubleSpinBox_contrast_percentile_max) + MainWindow.setTabOrder(self.doubleSpinBox_contrast_percentile_max, self.doubleSpinBox_contrast_absolute_max) + MainWindow.setTabOrder(self.doubleSpinBox_contrast_absolute_max, self.radioButton_contrast_percentile) + MainWindow.setTabOrder(self.radioButton_contrast_percentile, self.radioButton_contrast_absolute) + MainWindow.setTabOrder(self.radioButton_contrast_absolute, self.radioButton_full_range) + MainWindow.setTabOrder(self.radioButton_full_range, self.checkBox_fourier_logarithmic) + MainWindow.setTabOrder(self.checkBox_fourier_logarithmic, self.comboBox_set_current_scan) + MainWindow.setTabOrder(self.comboBox_set_current_scan, self.radioButton_pick_method_point) + MainWindow.setTabOrder(self.radioButton_pick_method_point, self.radioButton_pick_method_feature) + MainWindow.setTabOrder(self.radioButton_pick_method_feature, self.radioButton_pick_method_pick) + MainWindow.setTabOrder(self.radioButton_pick_method_pick, self.pushButton_load_feature_tracker_list) + MainWindow.setTabOrder(self.pushButton_load_feature_tracker_list, self.comboBox_scan_window_1) + MainWindow.setTabOrder(self.comboBox_scan_window_1, self.checkBox_feature_tracker_auto_fill) + MainWindow.setTabOrder(self.checkBox_feature_tracker_auto_fill, self.pushButton_save_feature_tracker_list) + MainWindow.setTabOrder(self.pushButton_save_feature_tracker_list, self.listWidget_features) + MainWindow.setTabOrder(self.listWidget_features, self.listWidget_feature_points) + MainWindow.setTabOrder(self.listWidget_feature_points, self.listWidget_feature_classes) + MainWindow.setTabOrder(self.listWidget_feature_classes, self.pushButton_remove_feature_class) + MainWindow.setTabOrder(self.pushButton_remove_feature_class, self.pushButton_remove_feature) + MainWindow.setTabOrder(self.pushButton_remove_feature, self.pushButton_remove_feature_point) + MainWindow.setTabOrder(self.pushButton_remove_feature_point, self.pushButton_feature_tracker_image_copy) + MainWindow.setTabOrder(self.pushButton_feature_tracker_image_copy, self.spinBox_feature_tracker_image_copy) + MainWindow.setTabOrder(self.spinBox_feature_tracker_image_copy, self.pushButton_copy_feature) + MainWindow.setTabOrder(self.pushButton_copy_feature, self.pushButton_force_copy_feature) + MainWindow.setTabOrder(self.pushButton_force_copy_feature, self.horizontalSlider_window_1) + MainWindow.setTabOrder(self.horizontalSlider_window_1, self.pushButton_set_feature_class_color) + MainWindow.setTabOrder(self.pushButton_set_feature_class_color, self.spinBox_window_2) + MainWindow.setTabOrder(self.spinBox_window_2, self.comboBox_scan_window_2) + MainWindow.setTabOrder(self.comboBox_scan_window_2, self.checkBox_snychronise) + MainWindow.setTabOrder(self.checkBox_snychronise, self.horizontalSlider_window_2) + MainWindow.setTabOrder(self.horizontalSlider_window_2, self.scrollArea) + MainWindow.setTabOrder(self.scrollArea, self.textBrowser_logs) + MainWindow.setTabOrder(self.textBrowser_logs, self.pushButton_clear_logs) + MainWindow.setTabOrder(self.pushButton_clear_logs, self.scrollArea_2) + MainWindow.setTabOrder(self.scrollArea_2, self.tabWidget_controller) + MainWindow.setTabOrder(self.tabWidget_controller, self.pushButton_scan_delete) + MainWindow.setTabOrder(self.pushButton_scan_delete, self.comboBox_scan_image_class) + MainWindow.setTabOrder(self.comboBox_scan_image_class, self.pushButton_delete_current_image_class) + MainWindow.setTabOrder(self.pushButton_delete_current_image_class, self.checkBox_enable_autosave) + MainWindow.setTabOrder(self.checkBox_enable_autosave, self.spinBox_autosave_in_seconds) + MainWindow.setTabOrder(self.spinBox_autosave_in_seconds, self.pushButton_show_measuring_points) + MainWindow.setTabOrder(self.pushButton_show_measuring_points, self.textBrowser_scan_information) + MainWindow.setTabOrder(self.textBrowser_scan_information, self.checkBox_filter_1d_use_visualyze_function) + MainWindow.setTabOrder(self.checkBox_filter_1d_use_visualyze_function, self.checkBox_filter_2d_use_visualyze_function) + MainWindow.setTabOrder(self.checkBox_filter_2d_use_visualyze_function, self.pushButton_filter_full_1d_signal) + MainWindow.setTabOrder(self.pushButton_filter_full_1d_signal, self.spinBox_full_signal_maximum_number_of_points) + MainWindow.setTabOrder(self.spinBox_full_signal_maximum_number_of_points, self.listWidget_filter_full_1d_signal) + MainWindow.setTabOrder(self.listWidget_filter_full_1d_signal, self.checkBox_show_rag_in_image) + MainWindow.setTabOrder(self.checkBox_show_rag_in_image, self.checkBox_show_features) + MainWindow.setTabOrder(self.checkBox_show_features, self.pushButton_visualization_contours_apply) + MainWindow.setTabOrder(self.pushButton_visualization_contours_apply, self.pushButton_drift_correction_by_feature) + MainWindow.setTabOrder(self.pushButton_drift_correction_by_feature, self.pushButton_generate_drift_feature) + MainWindow.setTabOrder(self.pushButton_generate_drift_feature, self.spinBox_ransac_reference_image_number) + MainWindow.setTabOrder(self.spinBox_ransac_reference_image_number, self.pushButton_ransac_correct_drift) + MainWindow.setTabOrder(self.pushButton_ransac_correct_drift, self.tabWidget_feature_tracker) + MainWindow.setTabOrder(self.tabWidget_feature_tracker, self.checkBox_add_to_all_images) + MainWindow.setTabOrder(self.checkBox_add_to_all_images, self.spinBox_maximum_number_of_points_per_feature) + MainWindow.setTabOrder(self.spinBox_maximum_number_of_points_per_feature, self.pushButton_feature_analyse) + MainWindow.setTabOrder(self.pushButton_feature_analyse, self.pushButton_auto_detect_features_current_image) + MainWindow.setTabOrder(self.pushButton_auto_detect_features_current_image, self.pushButton_auto_detect_feature_clear_image) + MainWindow.setTabOrder(self.pushButton_auto_detect_feature_clear_image, self.pushButton_auto_detect_features_all_images) + MainWindow.setTabOrder(self.pushButton_auto_detect_features_all_images, self.checkBox_auto_fill_selected_features) + MainWindow.setTabOrder(self.checkBox_auto_fill_selected_features, self.comboBox_feature_feature_highlight_method) + MainWindow.setTabOrder(self.comboBox_feature_feature_highlight_method, self.spinBox_feature_highlight_color_g) + MainWindow.setTabOrder(self.spinBox_feature_highlight_color_g, self.spinBox_feature_highlight_color_r) + MainWindow.setTabOrder(self.spinBox_feature_highlight_color_r, self.spinBox_feature_highlight_color_b) + MainWindow.setTabOrder(self.spinBox_feature_highlight_color_b, self.doubleSpinBox_feature_highlight_brightness) + MainWindow.setTabOrder(self.doubleSpinBox_feature_highlight_brightness, self.comboBox_feature_auto_detect_function) + MainWindow.setTabOrder(self.comboBox_feature_auto_detect_function, self.doubleSpinBox_feature_auto_detect_min_sigma) + MainWindow.setTabOrder(self.doubleSpinBox_feature_auto_detect_min_sigma, self.doubleSpinBox_feature_auto_detect_overlap) + MainWindow.setTabOrder(self.doubleSpinBox_feature_auto_detect_overlap, self.spinBox_feature_auto_detect_num_sigma) + MainWindow.setTabOrder(self.spinBox_feature_auto_detect_num_sigma, self.doubleSpinBox_feature_auto_detect_max_sigma) + MainWindow.setTabOrder(self.doubleSpinBox_feature_auto_detect_max_sigma, self.doubleSpinBox_feature_auto_detect_threshold) + MainWindow.setTabOrder(self.doubleSpinBox_feature_auto_detect_threshold, self.comboBox_feature_representation) + MainWindow.setTabOrder(self.comboBox_feature_representation, self.doubleSpinBox_feature_point_size) + MainWindow.setTabOrder(self.doubleSpinBox_feature_point_size, self.doubleSpinBox_feature_thickness) + MainWindow.setTabOrder(self.doubleSpinBox_feature_thickness, self.doubleSpinBox_feature_distance) + MainWindow.setTabOrder(self.doubleSpinBox_feature_distance, self.pushButton_save_feature_settings_as_default) + MainWindow.setTabOrder(self.pushButton_save_feature_settings_as_default, self.doubleSpinBox_saasmi_beta) + MainWindow.setTabOrder(self.doubleSpinBox_saasmi_beta, self.pushButton_saasmi) + MainWindow.setTabOrder(self.pushButton_saasmi, self.spinBox_number_of_neighbors) + MainWindow.setTabOrder(self.spinBox_number_of_neighbors, self.checkBox_auto_generate_rag) + MainWindow.setTabOrder(self.checkBox_auto_generate_rag, self.lineEdit_feature_class_to_add) + MainWindow.setTabOrder(self.lineEdit_feature_class_to_add, self.pushButton_add_feature_class) + MainWindow.setTabOrder(self.pushButton_add_feature_class, self.pushButton_step_size_update) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Visualyse")) + self.label_65.setText(_translate("MainWindow", "Cursor Position")) + self.label_cursor_position.setText(_translate("MainWindow", "(0, 0, 0)")) + self.pushButtonStartAnimation.setText(_translate("MainWindow", "Play")) + self.label.setText(_translate("MainWindow", "with")) + self.labelFps.setText(_translate("MainWindow", "frames per second")) + self.label_16.setText(_translate("MainWindow", "Scan")) + self.checkBox_snychronise.setText(_translate("MainWindow", "synchronise")) + self.label_17.setText(_translate("MainWindow", "Scan")) + self.label_28.setText(_translate("MainWindow", "Logs:")) + self.pushButton_clear_logs.setText(_translate("MainWindow", "Clear Logs")) + self.label_53.setText(_translate("MainWindow", "Scan Information")) + self.groupBox_scan_direction.setTitle(_translate("MainWindow", "Scan")) + self.label_15.setText(_translate("MainWindow", "Current Scan")) + self.pushButton_scan_delete.setText(_translate("MainWindow", "Delete")) + self.label_13.setText(_translate("MainWindow", "Image Class")) + self.pushButton_delete_current_image_class.setText(_translate("MainWindow", "Delete")) + self.checkBox_enable_autosave.setText(_translate("MainWindow", "Autosave every")) + self.label_52.setText(_translate("MainWindow", " Seconds")) + self.pushButton_show_measuring_points.setText(_translate("MainWindow", "Show Measuring Points")) + self.pushButton_plot_h5_infos.setText(_translate("MainWindow", "Plot h5 Infos")) + self.pushButton_show_point_cloud.setText(_translate("MainWindow", "Show Point Cloud")) + self.pushButton_show_density_plot.setText(_translate("MainWindow", "Show Density Plot")) + self.pushButton_step_size_update.setText(_translate("MainWindow", "Update")) + self.label_56.setText(_translate("MainWindow", "Step Size X")) + self.label_57.setText(_translate("MainWindow", "Step Size Y")) + self.pushButton_open_line_profile_dialog.setToolTip(_translate("MainWindow", "

Line profiles can be used to check the profile of a line on a given image or to adjust the step size to the real value of the image

")) + self.pushButton_open_line_profile_dialog.setText(_translate("MainWindow", "Line Profile Dialog")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_scan), _translate("MainWindow", "Scan")) + self.groupBox_filter_1d.setTitle(_translate("MainWindow", "Filter 1D")) + self.pushButton_open_filter_dialog_1d.setText(_translate("MainWindow", "Filter Dialog")) + self.checkBox_filter_1d_use_visualyze_function.setText(_translate("MainWindow", "Use Visualize Function")) + self.groupBox_filter_2d.setTitle(_translate("MainWindow", "Filter 2D")) + self.pushButton_open_filter_dialog_2d.setText(_translate("MainWindow", "Filter Dialog")) + self.checkBox_filter_2d_use_visualyze_function.setText(_translate("MainWindow", "Use Visualize Function")) + self.groupBox_1d_signal_full.setTitle(_translate("MainWindow", "Filter Full 1D Signal")) + self.pushButton_filter_full_1d_signal.setText(_translate("MainWindow", "Filter Dialog")) + self.label_31.setText(_translate("MainWindow", "Number of Points")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_filter), _translate("MainWindow", "Filter")) + self.groupBox_visualization.setTitle(_translate("MainWindow", "Visualization")) + self.pushButton_show_average_image.setText(_translate("MainWindow", "Show Average Image")) + self.label_29.setText(_translate("MainWindow", "Show Rag in Image")) + self.label_8.setText(_translate("MainWindow", "Display original image")) + self.label_30.setText(_translate("MainWindow", "Show Features")) + self.label_12.setText(_translate("MainWindow", "Invert Image")) + self.tabWidget_visualization.setTabText(self.tabWidget_visualization.indexOf(self.tab), _translate("MainWindow", "Image")) + self.label_7.setText(_translate("MainWindow", "Method")) + self.comboBox_fourier_method.setItemText(0, _translate("MainWindow", "Magnitude Spectrum")) + self.comboBox_fourier_method.setItemText(1, _translate("MainWindow", "Phase Spectrum")) + self.tabWidget_visualization.setTabText(self.tabWidget_visualization.indexOf(self.tab_2), _translate("MainWindow", "Fouier Transformed")) + self.label_4.setText(_translate("MainWindow", "Compare to Image:")) + self.label_5.setText(_translate("MainWindow", "Compare Method")) + self.comboBox_compare_method.setCurrentText(_translate("MainWindow", "SSIM")) + self.comboBox_compare_method.setItemText(0, _translate("MainWindow", "SSIM")) + self.comboBox_compare_method.setItemText(1, _translate("MainWindow", "Deviation Map")) + self.comboBox_compare_method.setItemText(2, _translate("MainWindow", "Cross-correlation")) + self.comboBox_compare_method.setItemText(3, _translate("MainWindow", "Background Subtraction")) + self.comboBox_compare_method.setItemText(4, _translate("MainWindow", "Background Subtraction (Detect Background for all images)")) + self.label_10.setText(_translate("MainWindow", "Compare Scan")) + self.tabWidget_visualization.setTabText(self.tabWidget_visualization.indexOf(self.tab_3), _translate("MainWindow", "Image Comparison")) + self.comboBox_contours.setItemText(0, _translate("MainWindow", "Canny")) + self.comboBox_contours.setItemText(1, _translate("MainWindow", "Laplacian of Gaussian Blob")) + self.comboBox_contours.setItemText(2, _translate("MainWindow", "Difference of Gaussian Blob")) + self.comboBox_contours.setItemText(3, _translate("MainWindow", "Determinant of Hessian Blob")) + self.label_6.setText(_translate("MainWindow", "Method")) + self.label_canny_sigma.setText(_translate("MainWindow", "Sigma")) + self.label_blob_max_sigma.setText(_translate("MainWindow", "Max Sigma")) + self.label_blob_overlap.setText(_translate("MainWindow", "Overlap")) + self.label_blob_min_sigma.setText(_translate("MainWindow", "Min Sigma")) + self.spinBox_blob_max_sigma.setToolTip(_translate("MainWindow", "The maximum standard deviation for Gaussian kernel. Keep this high to\n" +" detect larger blobs.\n" +" ")) + self.doubleSpinBox_blob_overlap.setToolTip(_translate("MainWindow", "A value between 0 and 1. If the area of two blobs overlaps by a\n" +" fraction greater than `threshold`, the smaller blob is eliminated.\n" +" ")) + self.spinBox_blob_num_sigma.setToolTip(_translate("MainWindow", "The number of intermediate values of standard deviations to consider\n" +" between `min_sigma` and `max_sigma`\n" +" ")) + self.label_blob_num_sigma.setText(_translate("MainWindow", "Num Sigma")) + self.doubleSpinBox_blob_threshold.setToolTip(_translate("MainWindow", "The absolute lower bound for scale space maxima. Local maxima smaller\n" +" than thresh are ignored. Reduce this to detect blobs with less\n" +" intensities.\n" +" ")) + self.label_blob_threshold.setText(_translate("MainWindow", "threshold")) + self.spinBox_blob_min_sigma.setToolTip(_translate("MainWindow", "The minimum standard deviation for Gaussian kernel. Keep this low to\n" +" detect smaller blobs.\n" +" ")) + self.pushButton_visualization_contours_apply.setText(_translate("MainWindow", "Apply")) + self.tabWidget_visualization.setTabText(self.tabWidget_visualization.indexOf(self.tab_4), _translate("MainWindow", "Contours")) + self.label_3.setToolTip(_translate("MainWindow", "Change the Color for areas without a measuring point in RGBA")) + self.label_3.setText(_translate("MainWindow", "NaN Color")) + self.label_14.setText(_translate("MainWindow", "Background Color")) + self.label_9.setText(_translate("MainWindow", "Z Value Scale")) + self.groupBox_3.setTitle(_translate("MainWindow", "Contrast Adjustments")) + self.radioButton_contrast_percentile.setText(_translate("MainWindow", "Percentile")) + self.radioButton_contrast_absolute.setText(_translate("MainWindow", "Absolute")) + self.radioButton_full_range.setText(_translate("MainWindow", "Full Range")) + self.checkBox_fourier_logarithmic.setText(_translate("MainWindow", "Logarithmic Color Range")) + self.pushButton_open_contrast_dialog.setText(_translate("MainWindow", "Open Contrast Dialog")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_visualization), _translate("MainWindow", "Visualization")) + self.groupBox_drift_detection.setTitle(_translate("MainWindow", "Drift Correction by Feature")) + self.pushButton_drift_correction_by_feature.setText(_translate("MainWindow", "Correct Drift by Drift Feature")) + self.pushButton_generate_drift_feature.setText(_translate("MainWindow", "Generate Drift Feature Class")) + self.groupBox.setTitle(_translate("MainWindow", "Drift Correction by RANSAC")) + self.label_27.setText(_translate("MainWindow", "Reference Image Number")) + self.pushButton_ransac_correct_drift.setText(_translate("MainWindow", "Correct Drift by RANSAC")) + self.groupBox_11.setTitle(_translate("MainWindow", "Drift Correction by Phase Cross Correlation")) + self.pushButton_drift_correction_by_phase_cross_correlation.setText(_translate("MainWindow", "Correct Drift by Phase Cross Correlation")) + self.label_64.setToolTip(_translate("MainWindow", "If the drift vector extends the value it is set to the same as the previous vector")) + self.label_64.setText(_translate("MainWindow", "Maximum Drift Vector Length (in Pixel)")) + self.lineEdit_offset_list.setToolTip(_translate("MainWindow", "

Set 0 to disable maximum vector length

")) + self.lineEdit_offset_list.setText(_translate("MainWindow", "[5,10,15,25]")) + self.label_63.setToolTip(_translate("MainWindow", "

Set 0 to disable maximum vector length

")) + self.label_63.setText(_translate("MainWindow", "Image Offset List")) + self.doubleSpinBox_max_drift_vector_length.setToolTip(_translate("MainWindow", "If the drift vector extends the value it is set to the same as the previous vector")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_drift_correction), _translate("MainWindow", "Drift Correction")) + self.pushButton_load_feature_tracker_list.setText(_translate("MainWindow", "Load from File")) + self.label_18.setText(_translate("MainWindow", "Feature Tracker List")) + self.lineEdit_feature_class_to_add.setText(_translate("MainWindow", "Feature_Class_Name")) + self.label_20.setText(_translate("MainWindow", "Auto-fill Feature Tracker List")) + self.pushButton_save_feature_tracker_list.setText(_translate("MainWindow", "Save in File")) + self.pushButton_remove_feature.setText(_translate("MainWindow", "Remove Feature")) + self.label_22.setText(_translate("MainWindow", "Feature Index")) + self.pushButton_rescale_features.setText(_translate("MainWindow", "Rescale Features")) + self.pushButton_feature_analyse.setText(_translate("MainWindow", "Open Analyse Dialog")) + self.label_23.setText(_translate("MainWindow", "Feature Points")) + self.pushButton_remove_feature_class.setText(_translate("MainWindow", "Remove Class")) + self.pushButton_remove_feature_point.setText(_translate("MainWindow", "Remove Point")) + self.label_25.setText(_translate("MainWindow", "Auto Detect Features")) + self.pushButton_auto_detect_features_current_image.setText(_translate("MainWindow", "Current Image")) + self.pushButton_auto_detect_feature_clear_image.setText(_translate("MainWindow", "Clear Image")) + self.pushButton_auto_detect_features_all_images.setText(_translate("MainWindow", "All Images")) + self.label_21.setText(_translate("MainWindow", "Feature Class")) + self.pushButton_jump_visualization.setText(_translate("MainWindow", "Open Jump Visualization Dialog")) + self.pushButton_feature_index_check_all.setText(_translate("MainWindow", "All")) + self.pushButton_feature_index_uncheck_all.setText(_translate("MainWindow", "None")) + self.pushButton_feature_class_check_all.setText(_translate("MainWindow", "All")) + self.pushButton_feature_class_uncheck_all.setText(_translate("MainWindow", "None")) + self.pushButton_open_neighbor_information_dialog.setText(_translate("MainWindow", "Neighbor Information Dialog")) + self.pushButton_generate_training_data.setText(_translate("MainWindow", "Generate Training Data")) + self.pushButton_export_lines_as_csv.setText(_translate("MainWindow", "Export Lines as CSV")) + self.pushButton_feature_tracker_image_copy.setText(_translate("MainWindow", "Copy")) + self.label_19.setText(_translate("MainWindow", "Copy List from Image")) + self.pushButton_copy_feature.setText(_translate("MainWindow", "Copy")) + self.label_24.setText(_translate("MainWindow", "Copy Feature List to other images")) + self.pushButton_force_copy_feature.setText(_translate("MainWindow", "Force Copy")) + self.pushButton_add_feature_class.setText(_translate("MainWindow", "Add Feature Class")) + self.pushButton_set_feature_class_color.setText(_translate("MainWindow", "Color")) + self.label_49.setText(_translate("MainWindow", "Auto-fill selected Features")) + self.groupBox_8.setTitle(_translate("MainWindow", "Feature Picking")) + self.label_11.setText(_translate("MainWindow", "Pick Method")) + self.radioButton_pick_method_point.setText(_translate("MainWindow", "Point")) + self.radioButton_pick_method_feature.setText(_translate("MainWindow", "Feature")) + self.radioButton_pick_method_pick.setText(_translate("MainWindow", "Pick")) + self.label_48.setText(_translate("MainWindow", "Add chosen Point to all Images")) + self.label_54.setToolTip(_translate("MainWindow", "0 for unlimited")) + self.label_54.setText(_translate("MainWindow", "Maximum Number of Points per Feature")) + self.groupBox_10.setTitle(_translate("MainWindow", "Feature Grid")) + self.label_62.setText(_translate("MainWindow", "Use Grid")) + self.label_61.setText(_translate("MainWindow", "Show Grid")) + self.label_66.setToolTip(_translate("MainWindow", "

Select the folder where the yolo labels are saved. The order of the file names should correspond with the image number (first file = first image)

")) + self.label_66.setText(_translate("MainWindow", "Load Yolo Label Folder")) + self.pushButton_load_yolo_label_folder.setToolTip(_translate("MainWindow", "

Select the folder where the yolo labels are saved. The order of the file names should correspond with the image number (first file = first image)

")) + self.pushButton_load_yolo_label_folder.setText(_translate("MainWindow", "Load")) + self.tabWidget_feature_tracker.setTabText(self.tabWidget_feature_tracker.indexOf(self.tabWidget_feature_tracker_feature), _translate("MainWindow", "Feature")) + self.groupBox_5.setTitle(_translate("MainWindow", "Feature Highlighting")) + self.label_36.setText(_translate("MainWindow", "Feature Highlight Method")) + self.label_37.setText(_translate("MainWindow", "Feature Fixed Color")) + self.label_38.setText(_translate("MainWindow", "R")) + self.label_39.setText(_translate("MainWindow", "G")) + self.label_40.setText(_translate("MainWindow", "B")) + self.label_41.setText(_translate("MainWindow", "Feature Brightness")) + self.groupBox_settings_feature_detection.setTitle(_translate("MainWindow", "Feature Detection")) + self.label_43.setText(_translate("MainWindow", "Feature Auto Detect Function")) + self.label_42.setText(_translate("MainWindow", "Minimum Sigma")) + self.label_47.setText(_translate("MainWindow", "Overlap")) + self.doubleSpinBox_feature_auto_detect_min_sigma.setToolTip(_translate("MainWindow", "

The minimum standard\n" +" deviation for Gaussian kernel. Keep this low to

detect\n" +" smaller blobs.

\n" +" ")) + self.doubleSpinBox_feature_auto_detect_overlap.setToolTip(_translate("MainWindow", "A value between 0 and 1. If the area of two blobs overlaps by a\n" +" fraction greater than `threshold`, the smaller blob is eliminated.\n" +" ")) + self.label_46.setText(_translate("MainWindow", "Threshold")) + self.spinBox_feature_auto_detect_num_sigma.setToolTip(_translate("MainWindow", "The number of intermediate values of standard deviations to consider\n" +" ")) + self.doubleSpinBox_feature_auto_detect_max_sigma.setToolTip(_translate("MainWindow", "The maximum standard deviation for Gaussian kernel. Keep this high to\n" +" detect larger blobs\n" +" ")) + self.label_45.setText(_translate("MainWindow", "Num Sigma")) + self.label_44.setText(_translate("MainWindow", "Maximum Sigma")) + self.doubleSpinBox_feature_auto_detect_threshold.setToolTip(_translate("MainWindow", "The absolute lower bound for scale space maxima. Local maxima smaller\n" +" than thresh are ignored. Reduce this to detect blobs with less\n" +" intensities.\n" +" ")) + self.label_59.setText(_translate("MainWindow", "Detect Maxima")) + self.pushButton_save_feature_settings_as_default.setText(_translate("MainWindow", "Apply current Settings")) + self.groupBox_4.setTitle(_translate("MainWindow", "Feature Representation")) + self.label_32.setText(_translate("MainWindow", "Feature Representation")) + self.comboBox_feature_representation.setItemText(0, _translate("MainWindow", "Disk")) + self.comboBox_feature_representation.setItemText(1, _translate("MainWindow", "Circle")) + self.comboBox_feature_representation.setItemText(2, _translate("MainWindow", "Sphere")) + self.label_33.setText(_translate("MainWindow", "Feature Point Size (in image Pixel)")) + self.label_34.setText(_translate("MainWindow", "Feature Thickness (in Percent)")) + self.label_35.setToolTip(_translate("MainWindow", "

When autodetecting features it\n" +" looks in the previous image, if a feature is found within the givin distance the\n" +" point will be added to the feature. The distance given here is multiplied by the\n" +" step size of the bins

\n" +" ")) + self.label_35.setText(_translate("MainWindow", "Auto Detect Feature Distance")) + self.groupBox_9.setTitle(_translate("MainWindow", "Feature Pick Settings")) + self.label_60.setToolTip(_translate("MainWindow", "If selected new features will snap to feature grid")) + self.label_60.setText(_translate("MainWindow", "Use Feature Grid")) + self.checkBox_use_feature_grid.setToolTip(_translate("MainWindow", "If selected new features will snap to feature grid")) + self.pushButton_generate_feature_grid.setText(_translate("MainWindow", "Generate Feature Grid")) + self.pushButton_apply_feature_grid_to_feature_points.setText(_translate("MainWindow", "Apply Feature Grid on Feature Points")) + self.tabWidget_feature_tracker.setTabText(self.tabWidget_feature_tracker.indexOf(self.tabWidget_feature_tracker_settings), _translate("MainWindow", "Settings")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_feature), _translate("MainWindow", "Feature Controller")) + self.label_2.setText(_translate("MainWindow", "Beta:")) + self.pushButton_saasmi.setText(_translate("MainWindow", "SAASMI")) + self.label_26.setText(_translate("MainWindow", "Number of k-neighbors")) + self.label_55.setText(_translate("MainWindow", "Auto generate RAG")) + self.pushButton_saasmi_show_prepared_images.setText(_translate("MainWindow", "Show Prepared Images")) + self.label_58.setText(_translate("MainWindow", "Morphology Disk Size")) + self.tabWidget_controller.setTabText(self.tabWidget_controller.indexOf(self.tab_saasmi), _translate("MainWindow", "SAASMI")) + self.menuFile.setTitle(_translate("MainWindow", "File")) + self.menuImport_File.setTitle(_translate("MainWindow", "Import File")) + self.menuExport.setTitle(_translate("MainWindow", "Export")) + self.menuTools.setTitle(_translate("MainWindow", "Tools")) + self.menuFilter.setTitle(_translate("MainWindow", "Filter")) + self.menuLayout.setTitle(_translate("MainWindow", "Layout")) + self.actionSave_Scan.setText(_translate("MainWindow", "Save Scan")) + self.actionImport_h5.setText(_translate("MainWindow", "Import h5")) + self.actionImport_vsy.setText(_translate("MainWindow", "Import vsy")) + self.actionExport_as_video.setText(_translate("MainWindow", "Export as video")) + self.actionExport_as_image.setText(_translate("MainWindow", "Export as image")) + self.actionExport_as_yspar_dat_mask_dat.setText(_translate("MainWindow", "Export as yspar.dat && mask.dat")) + self.actionSettings.setText(_translate("MainWindow", "Settings")) + self.actionExport_as_Scan_Object_vsy.setText(_translate("MainWindow", "Export as Scan Object (.vsy)")) + self.actionImport_filter_log.setText(_translate("MainWindow", "Import filter log")) + self.actionImport_filter_from_log.setText(_translate("MainWindow", "Import filter from log")) + self.actionImport_from_image_file.setText(_translate("MainWindow", "Import from image file")) + self.actionGenerate_Log_File.setText(_translate("MainWindow", "Generate Log File")) + self.actionImport_sxm.setText(_translate("MainWindow", "Import sxm")) + self.actionImport_gwy.setText(_translate("MainWindow", "Import gwy")) + self.actionExport_as_gwy.setText(_translate("MainWindow", "Export as .gwy")) + self.actionExport_as_zip_file.setText(_translate("MainWindow", "Export as zip file")) + self.actionImport_scan_zip.setText(_translate("MainWindow", "Import scan zip")) + self.actionImport_avi.setText(_translate("MainWindow", "Import avi")) + self.action_reset_horizontal_layout.setText(_translate("MainWindow", "Reset Horizontal Layout")) + self.actionImport_stp.setText(_translate("MainWindow", "Import stp")) + self.actionImport_npy.setText(_translate("MainWindow", "Import npy")) + self.actionImport_arn.setText(_translate("MainWindow", "Import arn")) diff --git a/view/ui/ui_widget_fullscreen.py b/view/ui/ui_widget_fullscreen.py new file mode 100644 index 0000000..29f2434 --- /dev/null +++ b/view/ui/ui_widget_fullscreen.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DialogFullscreen.ui' +# +# Created by: PyQt5 UI code generator 5.14.2 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DialogFullscreen(object): + def setupUi(self, DialogFullscreen): + DialogFullscreen.setObjectName("DialogFullscreen") + DialogFullscreen.resize(409, 267) + self.verticalLayout = QtWidgets.QVBoxLayout(DialogFullscreen) + self.verticalLayout.setObjectName("verticalLayout") + self.vtk_fullscreen_widget = QtWidgets.QWidget(DialogFullscreen) + self.vtk_fullscreen_widget.setObjectName("vtk_fullscreen_widget") + self.verticalLayout.addWidget(self.vtk_fullscreen_widget) + + self.retranslateUi(DialogFullscreen) + QtCore.QMetaObject.connectSlotsByName(DialogFullscreen) + + def retranslateUi(self, DialogFullscreen): + _translate = QtCore.QCoreApplication.translate + DialogFullscreen.setWindowTitle(_translate("DialogFullscreen", "Dialog")) diff --git a/view/ui/uitopy b/view/ui/uitopy new file mode 100755 index 0000000..2945be1 --- /dev/null +++ b/view/ui/uitopy @@ -0,0 +1,30 @@ +pyuic5 main_window.ui > ui_mainwindow.py +pyuic5 DialogExport.ui > ui_dialog_export.py +pyuic5 DialogFilter_2D.ui > ui_dialog_filter_2d.py +pyuic5 DialogFilter_1D.ui > ui_dialog_filter_1d.py +pyuic5 DialogFilter_1D_Full_Signal.ui > ui_dialog_filter_1d_full_signal.py +pyuic5 DialogSettings.ui > ui_dialog_settings.py +pyuic5 DialogImportH5.ui > ui_dialog_import_h5.py +pyuic5 DialogImportGWY.ui > ui_dialog_import_gwy.py +pyuic5 DialogFullscreen.ui > ui_widget_fullscreen.py +pyuic5 DialogProgress.ui > ui_dialog_progress.py +pyuic5 DialogExportZip.ui > ui_dialog_export_zip.py +pyuic5 DialogAnalyse.ui > ui_dialog_analyse.py +pyuic5 DialogSaasmi.ui > ui_dialog_saasmi.py +pyuic5 DialogImportFeatures.ui > ui_dialog_import_features.py +pyuic5 DialogColorMapPicker.ui > ui_dialog_color_map_picker.py +pyuic5 DialogMeasuringPoints.ui > ui_dialog_measuring_points.py +pyuic5 DialogExportXYZ.ui > ui_dialog_export_xyz.py +pyuic5 DialogRescaleFeatures.ui > ui_dialog_rescale_features.py +pyuic5 DialogLineProfile.ui > ui_dialog_line_profile.py +pyuic5 DialogFeatureClassVisualization.ui > ui_dialog_feature_class_visualization.py +pyuic5 DialogJumpVisualization.ui > ui_dialog_jump_visualization.py +pyuic5 DialogAverageImage.ui > ui_dialog_average_image.py +pyuic5 DialogFeatureRag.ui > ui_dialog_feature_rag.py +pyuic5 DialogContrast.ui > ui_dialog_contrast.py +pyuic5 DialogFeatureGrid.ui > ui_dialog_feature_grid.py +pyuic5 DialogNeuralNetworks.ui > ui_dialog_neural_networks.py +pyuic5 PlotableDialog.ui > ui_dialog_plotable.py +pyuic5 DialogPointCloud.ui > ui_dialog_point_cloud.py +pyuic5 DialogImportNpy.ui > ui_dialog_import_npy.py +pyuic5 DialogDensity.ui > ui_dialog_density.py \ No newline at end of file diff --git a/vsy_object b/vsy_object new file mode 100644 index 0000000..509f3ef Binary files /dev/null and b/vsy_object differ