From 82cc6a5509bdb4a2ed776081e022eabbc62e5877 Mon Sep 17 00:00:00 2001 From: Ajith Pattammattel Date: Sat, 14 Oct 2023 14:36:28 -0400 Subject: [PATCH] python 3.10 updates --- xmidas/css/darkStyle.css | 136 +- xmidas/css/defaultStyle.css | 144 +- xmidas/css/modern.css | 578 +- xmidas/main.py | 8318 +++++++++++----------- xmidas/uis/ClusterView.ui | 532 +- xmidas/uis/ComponentScatterPlot.ui | 390 +- xmidas/uis/ComponentView.ui | 599 +- xmidas/uis/Log.ui | 136 +- xmidas/uis/MaskedView.ui | 704 +- xmidas/uis/MultiImageSpectrumView.ui | 734 ++ xmidas/uis/RefChooser.ui | 499 +- xmidas/uis/SVGs/Arrow Down.svg | 12 - xmidas/uis/SVGs/Arrow Left.svg | 12 - xmidas/uis/SVGs/Arrow Right.svg | 12 - xmidas/uis/SVGs/Arrow Up.svg | 12 - xmidas/uis/SVGs/Basket.svg | 14 - xmidas/uis/SVGs/Book.svg | 11 - xmidas/uis/SVGs/Calendar.svg | 14 - xmidas/uis/SVGs/Camera.svg | 14 - xmidas/uis/SVGs/Case.svg | 11 - xmidas/uis/SVGs/Change View.svg | 14 - xmidas/uis/SVGs/Check V2.svg | 13 - xmidas/uis/SVGs/Check.svg | 14 - xmidas/uis/SVGs/Chervon Right Circle.svg | 16 - xmidas/uis/SVGs/Chevron Down Circle.svg | 16 - xmidas/uis/SVGs/Chevron Down.svg | 8 - xmidas/uis/SVGs/Chevron Left Circle.svg | 16 - xmidas/uis/SVGs/Chevron Left.svg | 8 - xmidas/uis/SVGs/Chevron Right.svg | 8 - xmidas/uis/SVGs/Chevron Up Circle.svg | 16 - xmidas/uis/SVGs/Chevron Up.svg | 8 - xmidas/uis/SVGs/Clock.svg | 14 - xmidas/uis/SVGs/Close.svg | 9 - xmidas/uis/SVGs/Cog.svg | 40 - xmidas/uis/SVGs/Cross Circle.svg | 14 - xmidas/uis/SVGs/Download.svg | 13 - xmidas/uis/SVGs/Edit V2.svg | 13 - xmidas/uis/SVGs/Edit.svg | 19 - xmidas/uis/SVGs/File.svg | 16 - xmidas/uis/SVGs/Grid.svg | 16 - xmidas/uis/SVGs/Heart.svg | 18 - xmidas/uis/SVGs/Image.svg | 14 - xmidas/uis/SVGs/Link.svg | 14 - xmidas/uis/SVGs/Location Cursor.svg | 9 - xmidas/uis/SVGs/Location.svg | 11 - xmidas/uis/SVGs/Logout.svg | 16 - xmidas/uis/SVGs/Mail.svg | 9 - xmidas/uis/SVGs/Menu.svg | 17 - xmidas/uis/SVGs/Message.svg | 13 - xmidas/uis/SVGs/Microphone.svg | 13 - xmidas/uis/SVGs/More.svg | 11 - xmidas/uis/SVGs/Plus Circle.svg | 14 - xmidas/uis/SVGs/Plus.svg | 8 - xmidas/uis/SVGs/Power Button.svg | 32 - xmidas/uis/SVGs/Printer.svg | 11 - xmidas/uis/SVGs/Settings.svg | 14 - xmidas/uis/SVGs/Share.svg | 13 - xmidas/uis/SVGs/Shutter.svg | 13 - xmidas/uis/SVGs/Star.svg | 15 - xmidas/uis/SVGs/Trash.svg | 14 - xmidas/uis/SVGs/Trophy.svg | 12 - xmidas/uis/SVGs/User.svg | 14 - xmidas/uis/SVGs/Video.svg | 10 - xmidas/uis/Scatter3D.ui | 260 +- xmidas/uis/Scatter3D_plotly.ui | 240 +- xmidas/uis/ScatterView.ui | 498 +- xmidas/uis/StackViewer.ui | 670 +- xmidas/uis/XANESViewer.ui | 844 ++- xmidas/uis/__init__.py | 0 xmidas/uis/align_test.ui | 142 +- xmidas/uis/animation2.gif | Bin 94900 -> 0 bytes xmidas/uis/animationWindow.ui | 136 +- xmidas/uis/mainwindow_admin.ui | 2814 -------- xmidas/uis/maskedScatterPlotFit.ui | 374 +- xmidas/uis/midas.py | 1048 --- xmidas/uis/midasMainwindow.ui | 5972 ++++++++-------- xmidas/uis/multipleScatterFit.ui | 228 +- xmidas/uis/mutlichannel.ui | 922 +-- xmidas/uis/singleStackView.ui | 302 +- xmidas/uis/xanesFitStat.ui | 94 +- xmidas/uis/xrf_xanes_gui_3ID.ui | 1512 ++-- xmidas/utils.py | 815 +++ 82 files changed, 13572 insertions(+), 16787 deletions(-) create mode 100644 xmidas/uis/MultiImageSpectrumView.ui delete mode 100644 xmidas/uis/SVGs/Arrow Down.svg delete mode 100644 xmidas/uis/SVGs/Arrow Left.svg delete mode 100644 xmidas/uis/SVGs/Arrow Right.svg delete mode 100644 xmidas/uis/SVGs/Arrow Up.svg delete mode 100644 xmidas/uis/SVGs/Basket.svg delete mode 100644 xmidas/uis/SVGs/Book.svg delete mode 100644 xmidas/uis/SVGs/Calendar.svg delete mode 100644 xmidas/uis/SVGs/Camera.svg delete mode 100644 xmidas/uis/SVGs/Case.svg delete mode 100644 xmidas/uis/SVGs/Change View.svg delete mode 100644 xmidas/uis/SVGs/Check V2.svg delete mode 100644 xmidas/uis/SVGs/Check.svg delete mode 100644 xmidas/uis/SVGs/Chervon Right Circle.svg delete mode 100644 xmidas/uis/SVGs/Chevron Down Circle.svg delete mode 100644 xmidas/uis/SVGs/Chevron Down.svg delete mode 100644 xmidas/uis/SVGs/Chevron Left Circle.svg delete mode 100644 xmidas/uis/SVGs/Chevron Left.svg delete mode 100644 xmidas/uis/SVGs/Chevron Right.svg delete mode 100644 xmidas/uis/SVGs/Chevron Up Circle.svg delete mode 100644 xmidas/uis/SVGs/Chevron Up.svg delete mode 100644 xmidas/uis/SVGs/Clock.svg delete mode 100644 xmidas/uis/SVGs/Close.svg delete mode 100644 xmidas/uis/SVGs/Cog.svg delete mode 100644 xmidas/uis/SVGs/Cross Circle.svg delete mode 100644 xmidas/uis/SVGs/Download.svg delete mode 100644 xmidas/uis/SVGs/Edit V2.svg delete mode 100644 xmidas/uis/SVGs/Edit.svg delete mode 100644 xmidas/uis/SVGs/File.svg delete mode 100644 xmidas/uis/SVGs/Grid.svg delete mode 100644 xmidas/uis/SVGs/Heart.svg delete mode 100644 xmidas/uis/SVGs/Image.svg delete mode 100644 xmidas/uis/SVGs/Link.svg delete mode 100644 xmidas/uis/SVGs/Location Cursor.svg delete mode 100644 xmidas/uis/SVGs/Location.svg delete mode 100644 xmidas/uis/SVGs/Logout.svg delete mode 100644 xmidas/uis/SVGs/Mail.svg delete mode 100644 xmidas/uis/SVGs/Menu.svg delete mode 100644 xmidas/uis/SVGs/Message.svg delete mode 100644 xmidas/uis/SVGs/Microphone.svg delete mode 100644 xmidas/uis/SVGs/More.svg delete mode 100644 xmidas/uis/SVGs/Plus Circle.svg delete mode 100644 xmidas/uis/SVGs/Plus.svg delete mode 100644 xmidas/uis/SVGs/Power Button.svg delete mode 100644 xmidas/uis/SVGs/Printer.svg delete mode 100644 xmidas/uis/SVGs/Settings.svg delete mode 100644 xmidas/uis/SVGs/Share.svg delete mode 100644 xmidas/uis/SVGs/Shutter.svg delete mode 100644 xmidas/uis/SVGs/Star.svg delete mode 100644 xmidas/uis/SVGs/Trash.svg delete mode 100644 xmidas/uis/SVGs/Trophy.svg delete mode 100644 xmidas/uis/SVGs/User.svg delete mode 100644 xmidas/uis/SVGs/Video.svg delete mode 100644 xmidas/uis/__init__.py delete mode 100644 xmidas/uis/animation2.gif delete mode 100644 xmidas/uis/mainwindow_admin.ui delete mode 100644 xmidas/uis/midas.py create mode 100644 xmidas/utils.py diff --git a/xmidas/css/darkStyle.css b/xmidas/css/darkStyle.css index a5e9ddb..ea2c89f 100644 --- a/xmidas/css/darkStyle.css +++ b/xmidas/css/darkStyle.css @@ -1,68 +1,68 @@ -QWidget { -background-color: rgb(60, 60, 60); -color: rgb(255, 255, 255); -font: 10pt "Segoe UI"; -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -} - -QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - +QWidget { +background-color: rgb(60, 60, 60); +color: rgb(255, 255, 255); +font: 10pt "Segoe UI"; +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +} + +QSlider::groove:horizontal { +border: 1px solid #bbb; +background: white; +height: 10px; +border-radius: 4px; +} + +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #66e, stop: 1 #bbf); +background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, + stop: 0 #bbf, stop: 1 #55f); +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::add-page:horizontal { +background: #fff; +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::handle:horizontal { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #eee, stop:1 #ccc); +border: 1px solid #777; +width: 13px; +margin-top: -2px; +margin-bottom: -2px; +border-radius: 4px; +} + +QSlider::handle:horizontal:hover { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #fff, stop:1 #ddd); +border: 1px solid #444; +border-radius: 2px; +} + +QSlider::sub-page:horizontal:disabled { +background: #bbb; +border-color: #999; +} + +QSlider::add-page:horizontal:disabled { +background: #eee; +border-color: #999; +} + +QSlider::handle:horizontal:disabled { +background: #eee; +border: 1px solid #aaa; +border-radius: 4px; +} + diff --git a/xmidas/css/defaultStyle.css b/xmidas/css/defaultStyle.css index 981a787..11c2467 100644 --- a/xmidas/css/defaultStyle.css +++ b/xmidas/css/defaultStyle.css @@ -1,72 +1,72 @@ - -QWidget { -font: 10pt "Segoe UI"; -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -font: 10pt "Segoe UI"; -} - -QLabel { -font: 10pt "Segoe UI"; -} - -QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - + +QWidget { +font: 10pt "Segoe UI"; +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +font: 10pt "Segoe UI"; +} + +QLabel { +font: 10pt "Segoe UI"; +} + +QSlider::groove:horizontal { +border: 1px solid #bbb; +background: white; +height: 10px; +border-radius: 4px; +} + +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #66e, stop: 1 #bbf); +background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, + stop: 0 #bbf, stop: 1 #55f); +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::add-page:horizontal { +background: #fff; +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::handle:horizontal { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #eee, stop:1 #ccc); +border: 1px solid #777; +width: 13px; +margin-top: -2px; +margin-bottom: -2px; +border-radius: 4px; +} + +QSlider::handle:horizontal:hover { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #fff, stop:1 #ddd); +border: 1px solid #444; +border-radius: 2px; +} + +QSlider::sub-page:horizontal:disabled { +background: #bbb; +border-color: #999; +} + +QSlider::add-page:horizontal:disabled { +background: #eee; +border-color: #999; +} + +QSlider::handle:horizontal:disabled { +background: #eee; +border: 1px solid #aaa; +border-radius: 4px; +} + diff --git a/xmidas/css/modern.css b/xmidas/css/modern.css index 1bc5d20..a949b73 100644 --- a/xmidas/css/modern.css +++ b/xmidas/css/modern.css @@ -1,289 +1,289 @@ -/* ///////////////////////////////////////////////////////////////////////////////////////////////// - -SET APP STYLESHEET - FULL STYLES HERE -DARK THEME - DRACULA COLOR BASED - -///////////////////////////////////////////////////////////////////////////////////////////////// */ - -QWidget{ - background-color: rgb(23,23,37); - color: rgb(255, 255, 200); - font: 10pt "Segoe UI"; -} - - - -/* ///////////////////////////////////////////////////////////////////////////////////////////////// -Tooltip */ -QToolTip { - color: #ffffff; - background-color: rgba(33, 37, 43, 180); - border: 1px solid rgb(44, 49, 58); - background-image: none; - background-position: left center; - background-repeat: no-repeat; - border: none; - border-left: 2px solid rgb(255, 121, 198); - text-align: left; - padding-left: 8px; - margin: 0px; -} - - -QPushButton { - background-color: rgb(210, 210, 98); - color: rgb(23,23,37); - border: none; - border-radius: 5px; - padding: 4px; -} -QPushButton:hover { - background-color: rgb(211, 100, 211); -} -QPushButton:pressed { - background-color: rgb(189, 147, 249); - color: rgb(255, 255, 255); -} - - -QTabWidget { - color: rgb(44, 0, 0); -} -QTabWidget::item{ - color: rgb(24, 0, 0); -} -QTabBar::tab { - color:rgb(24, 0, 0); -} - -QLineEdit { - background-color: rgb(33, 37, 43); - border-radius: 5px; - border: 2px solid rgb(33, 37, 43); - padding-left: 10px; - color: rgb(255,255,255); - selection-color: rgb(255, 255, 255); - selection-background-color: rgb(255, 121, 198); -} -QLineEdit:hover { - border: 2px solid rgb(64, 71, 88); -} -QLineEdit:focus { - border: 2px solid rgb(91, 101, 124); -} - -/* ///////////////////////////////////////////////////////////////////////////////////////////////// -PlainTextEdit */ -QPlainTextEdit { - background-color: rgb(27, 29, 35); - border-radius: 5px; - padding: 10px; - selection-color: rgb(255, 255, 255); - selection-background-color: rgb(255, 121, 198); -} -QPlainTextEdit QScrollBar:vertical { - width: 8px; - } -QPlainTextEdit QScrollBar:horizontal { - height: 8px; - } -QPlainTextEdit:hover { - border: 2px solid rgb(64, 71, 88); -} -QPlainTextEdit:focus { - border: 2px solid rgb(91, 101, 124); -} - -/* ///////////////////////////////////////////////////////////////////////////////////////////////// -ScrollBars */ -QScrollBar:horizontal { - border: none; - background: rgb(52, 59, 72); - height: 8px; - margin: 0px 21px 0 21px; - border-radius: 0px; -} -QScrollBar::handle:horizontal { - background: rgb(189, 147, 249); - min-width: 25px; - border-radius: 4px -} -QScrollBar::add-line:horizontal { - border: none; - background: rgb(55, 63, 77); - width: 20px; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - subcontrol-position: right; - subcontrol-origin: margin; -} -QScrollBar::sub-line:horizontal { - border: none; - background: rgb(55, 63, 77); - width: 20px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - subcontrol-position: left; - subcontrol-origin: margin; -} -QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal -{ - background: none; -} -QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal -{ - background: none; -} - QScrollBar:vertical { - border: none; - background: rgb(52, 59, 72); - width: 8px; - margin: 21px 0 21px 0; - border-radius: 0px; - } - QScrollBar::handle:vertical { - background: rgb(189, 147, 249); - min-height: 25px; - border-radius: 4px - } - QScrollBar::add-line:vertical { - border: none; - background: rgb(55, 63, 77); - height: 20px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - subcontrol-position: bottom; - subcontrol-origin: margin; - } - QScrollBar::sub-line:vertical { - border: none; - background: rgb(55, 63, 77); - height: 20px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - subcontrol-position: top; - subcontrol-origin: margin; - } - QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { - background: none; - } - - QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; - } - - -QRadioButton::indicator { - border: 3px solid rgb(52, 59, 72); - width: 15px; - height: 15px; - border-radius: 10px; - background: rgb(44, 4, 0); -} -QRadioButton::indicator:hover { - border: 3px solid rgb(58, 66, 81); -} -QRadioButton::indicator:checked { - background: 3px solid rgb(0, 255, 0); - border: 3px solid rgb(255, 252, 255); -} - -QCheckBox::indicator { - border: 3px solid rgb(52, 59, 72); - width: 15px; - height: 15px; - border-radius: 10px; - background: rgb(44, 4, 0); -} -QCheckBox::indicator:hover { - border: 3px solid rgb(58, 66, 81); -} -QCheckBox::indicator:checked { - background: 3px solid rgb(0, 255, 0); - border: 3px solid rgb(255, 252, 255); -} - -/* ///////////////////////////////////////////////////////////////////////////////////////////////// -ComboBox */ -QComboBox{ - background-color: rgb(138, 138, 138); - color: rgb(79, 27, 79); - border-radius: 5px; - border: 2px solid rgb(33, 37, 43); - padding: 5px; - padding-left: 10px; -} -QComboBox:hover{ - border: 2px solid rgb(64, 71, 88); -} -QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: top right; - width: 25px; - border-left-width: 3px; - border-left-color: rgba(39, 44, 54, 150); - border-left-style: solid; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - background-image: url(:/icons/images/icons/cil-arrow-bottom.png); - background-position: center; - background-repeat: no-reperat; - } -QComboBox QAbstractItemView { - color: rgb(255, 121, 198); - background-color: rgb(33, 37, 43); - padding: 10px; - selection-background-color: rgb(39, 44, 54); -} - -/* ///////////////////////////////////////////////////////////////////////////////////////////////// -Sliders */ -QSlider::groove:horizontal { - border-radius: 5px; - height: 10px; - margin: 0px; - background-color: rgb(52, 59, 72); -} -QSlider::groove:horizontal:hover { - background-color: rgb(55, 62, 76); -} -QSlider::handle:horizontal { - background-color: rgb(189, 147, 249); - border: none; - height: 10px; - width: 10px; - margin: 0px; - border-radius: 5px; -} -QSlider::handle:horizontal:hover { - background-color: rgb(195, 155, 255); -} -QSlider::handle:horizontal:pressed { - background-color: rgb(255, 121, 198); -} - -QSlider::groove:vertical { - border-radius: 5px; - width: 10px; - margin: 0px; - background-color: rgb(52, 59, 72); -} -QSlider::groove:vertical:hover { - background-color: rgb(55, 62, 76); -} -QSlider::handle:vertical { - background-color: rgb(189, 147, 249); - border: none; - height: 10px; - width: 10px; - margin: 0px; - border-radius: 5px; -} -QSlider::handle:vertical:hover { - background-color: rgb(195, 155, 255); -} -QSlider::handle:vertical:pressed { - background-color: rgb(255, 121, 198); -} - - +/* ///////////////////////////////////////////////////////////////////////////////////////////////// + +SET APP STYLESHEET - FULL STYLES HERE +DARK THEME - DRACULA COLOR BASED + +///////////////////////////////////////////////////////////////////////////////////////////////// */ + +QWidget{ + background-color: rgb(23,23,37); + color: rgb(255, 255, 200); + font: 10pt "Segoe UI"; +} + + + +/* ///////////////////////////////////////////////////////////////////////////////////////////////// +Tooltip */ +QToolTip { + color: #ffffff; + background-color: rgba(33, 37, 43, 180); + border: 1px solid rgb(44, 49, 58); + background-image: none; + background-position: left center; + background-repeat: no-repeat; + border: none; + border-left: 2px solid rgb(255, 121, 198); + text-align: left; + padding-left: 8px; + margin: 0px; +} + + +QPushButton { + background-color: rgb(210, 210, 98); + color: rgb(23,23,37); + border: none; + border-radius: 5px; + padding: 4px; +} +QPushButton:hover { + background-color: rgb(211, 100, 211); +} +QPushButton:pressed { + background-color: rgb(189, 147, 249); + color: rgb(255, 255, 255); +} + + +QTabWidget { + color: rgb(44, 0, 0); +} +QTabWidget::item{ + color: rgb(24, 0, 0); +} +QTabBar::tab { + color:rgb(24, 0, 0); +} + +QLineEdit { + background-color: rgb(33, 37, 43); + border-radius: 5px; + border: 2px solid rgb(33, 37, 43); + padding-left: 10px; + color: rgb(255,255,255); + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(255, 121, 198); +} +QLineEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QLineEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + +/* ///////////////////////////////////////////////////////////////////////////////////////////////// +PlainTextEdit */ +QPlainTextEdit { + background-color: rgb(27, 29, 35); + border-radius: 5px; + padding: 10px; + selection-color: rgb(255, 255, 255); + selection-background-color: rgb(255, 121, 198); +} +QPlainTextEdit QScrollBar:vertical { + width: 8px; + } +QPlainTextEdit QScrollBar:horizontal { + height: 8px; + } +QPlainTextEdit:hover { + border: 2px solid rgb(64, 71, 88); +} +QPlainTextEdit:focus { + border: 2px solid rgb(91, 101, 124); +} + +/* ///////////////////////////////////////////////////////////////////////////////////////////////// +ScrollBars */ +QScrollBar:horizontal { + border: none; + background: rgb(52, 59, 72); + height: 8px; + margin: 0px 21px 0 21px; + border-radius: 0px; +} +QScrollBar::handle:horizontal { + background: rgb(189, 147, 249); + min-width: 25px; + border-radius: 4px +} +QScrollBar::add-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: right; + subcontrol-origin: margin; +} +QScrollBar::sub-line:horizontal { + border: none; + background: rgb(55, 63, 77); + width: 20px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + subcontrol-position: left; + subcontrol-origin: margin; +} +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal +{ + background: none; +} +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ + background: none; +} + QScrollBar:vertical { + border: none; + background: rgb(52, 59, 72); + width: 8px; + margin: 21px 0 21px 0; + border-radius: 0px; + } + QScrollBar::handle:vertical { + background: rgb(189, 147, 249); + min-height: 25px; + border-radius: 4px + } + QScrollBar::add-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + subcontrol-position: bottom; + subcontrol-origin: margin; + } + QScrollBar::sub-line:vertical { + border: none; + background: rgb(55, 63, 77); + height: 20px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + subcontrol-position: top; + subcontrol-origin: margin; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + + +QRadioButton::indicator { + border: 3px solid rgb(52, 59, 72); + width: 15px; + height: 15px; + border-radius: 10px; + background: rgb(44, 4, 0); +} +QRadioButton::indicator:hover { + border: 3px solid rgb(58, 66, 81); +} +QRadioButton::indicator:checked { + background: 3px solid rgb(0, 255, 0); + border: 3px solid rgb(255, 252, 255); +} + +QCheckBox::indicator { + border: 3px solid rgb(52, 59, 72); + width: 15px; + height: 15px; + border-radius: 10px; + background: rgb(44, 4, 0); +} +QCheckBox::indicator:hover { + border: 3px solid rgb(58, 66, 81); +} +QCheckBox::indicator:checked { + background: 3px solid rgb(0, 255, 0); + border: 3px solid rgb(255, 252, 255); +} + +/* ///////////////////////////////////////////////////////////////////////////////////////////////// +ComboBox */ +QComboBox{ + background-color: rgb(138, 138, 138); + color: rgb(79, 27, 79); + border-radius: 5px; + border: 2px solid rgb(33, 37, 43); + padding: 5px; + padding-left: 10px; +} +QComboBox:hover{ + border: 2px solid rgb(64, 71, 88); +} +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 25px; + border-left-width: 3px; + border-left-color: rgba(39, 44, 54, 150); + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-image: url(:/icons/images/icons/cil-arrow-bottom.png); + background-position: center; + background-repeat: no-reperat; + } +QComboBox QAbstractItemView { + color: rgb(255, 121, 198); + background-color: rgb(33, 37, 43); + padding: 10px; + selection-background-color: rgb(39, 44, 54); +} + +/* ///////////////////////////////////////////////////////////////////////////////////////////////// +Sliders */ +QSlider::groove:horizontal { + border-radius: 5px; + height: 10px; + margin: 0px; + background-color: rgb(52, 59, 72); +} +QSlider::groove:horizontal:hover { + background-color: rgb(55, 62, 76); +} +QSlider::handle:horizontal { + background-color: rgb(189, 147, 249); + border: none; + height: 10px; + width: 10px; + margin: 0px; + border-radius: 5px; +} +QSlider::handle:horizontal:hover { + background-color: rgb(195, 155, 255); +} +QSlider::handle:horizontal:pressed { + background-color: rgb(255, 121, 198); +} + +QSlider::groove:vertical { + border-radius: 5px; + width: 10px; + margin: 0px; + background-color: rgb(52, 59, 72); +} +QSlider::groove:vertical:hover { + background-color: rgb(55, 62, 76); +} +QSlider::handle:vertical { + background-color: rgb(189, 147, 249); + border: none; + height: 10px; + width: 10px; + margin: 0px; + border-radius: 5px; +} +QSlider::handle:vertical:hover { + background-color: rgb(195, 155, 255); +} +QSlider::handle:vertical:pressed { + background-color: rgb(255, 121, 198); +} + + diff --git a/xmidas/main.py b/xmidas/main.py index 3185d3c..c2c2236 100644 --- a/xmidas/main.py +++ b/xmidas/main.py @@ -1,4299 +1,4019 @@ -# -*- coding: utf-8 -*- - -# Author: Ajith Pattammattel -# First Version on:06-23-2020 - -import argparse -import logging -import sys -import webbrowser -import traceback -import os -import json -import h5py -import scipy.stats as stats -import numpy as np -import pandas as pd -import tifffile as tf -import pyqtgraph as pg -import pyqtgraph.exporters -import scipy.optimize as opt -import sklearn.decomposition as sd -import sklearn.cluster as sc - -from pyqtgraph import plot -from itertools import combinations -from scipy.stats import linregress -from scipy.signal import savgol_filter -from skimage.transform import resize -from skimage import filters -from sklearn import linear_model -from larch.xafs import preedge -from pystackreg import StackReg -from packaging import version - -from PyQt5 import QtWidgets, QtCore, QtGui, uic, QtTest -from PyQt5.QtGui import QMovie -from PyQt5.QtWidgets import QMessageBox, QFileDialog, QApplication -from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool, PYQT_VERSION_STR - -from . import __version__ - -# from MultiChannel import * - -logger = logging.getLogger() -try: - import cv2 # noqa: F401 -except Exception: - logger.warning("openCV module not found") - pass -if hasattr(QtCore.Qt, "AA_EnableHighDpiScaling"): - QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) - -if hasattr(QtCore.Qt, "AA_UseHighDpiPixmaps"): - QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) - -ui_path = os.path.dirname(os.path.abspath(__file__)) - -# global settings for pyqtgraph plot and image colormaps -pg.setConfigOption("imageAxisOrder", "row-major") -cmap_names = ["CET-L13", "CET-L14", "CET-L15"] -cmap_combo = combinations(cmap_names, 2) -cmap_label1 = ["red", "green", "blue"] -cmap_label2 = ["yellow", "magenta", "cyan"] -cmap_dict = {} -for i, name in zip(cmap_names, cmap_label1): - cmap_dict[name] = pg.colormap.get(i).getLookupTable(alpha=True) - -for i, name in zip(cmap_combo, cmap_label2): - cmap_dict[name] = pg.colormap.get(i[0]).getLookupTable(alpha=True) + pg.colormap.get(i[1]).getLookupTable( - alpha=True - ) - cmap_dict[name][:, 3] = 255 - - grey = ( - pg.colormap.get("CET-L13").getLookupTable(alpha=True) - + pg.colormap.get("CET-L14").getLookupTable(alpha=True) - + pg.colormap.get("CET-L15").getLookupTable(alpha=True) - ) - - grey[:, 3] = 255 - cmap_dict["grey"] = grey - - -class jsonEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.integer): - return int(obj) - elif isinstance(obj, np.floating): - return float(obj) - elif isinstance(obj, np.ndarray): - return obj.tolist() - else: - return super(jsonEncoder, self).default(obj) - - -class midasWindow(QtWidgets.QMainWindow): - def __init__(self, im_stack=None, energy=[], refs=[]): - super(midasWindow, self).__init__() - uic.loadUi(os.path.join(ui_path, "uis/midasMainwindow.ui"), self) - self.im_stack = im_stack - self.energy = energy - self.refs = refs - self.loaded_tranform_file = [] - self.image_roi2_flag = False - self.refStackAvailable = False - self.isAReload = False - self.plotWidth = 2 - self.stackStatusDict = {} - - self.plt_colors = [ - "g", - "r", - "c", - "m", - "y", - "w", - "b", - pg.mkPen(70, 5, 80), - pg.mkPen(255, 85, 130), - pg.mkPen(0, 85, 130), - pg.mkPen(255, 170, 60), - ] * 3 - # window style - self.actionDarkMode.triggered.connect(self.darkMode) - self.actionDefault.triggered.connect(self.defaultMode) - self.actionModern.triggered.connect(self.modernMode) - - # self.setToolTipsVisible(True) - for menuItem in self.findChildren(QtWidgets.QMenu): - menuItem.setToolTipsVisible(True) - - # plotview options - self.actionWhite.triggered.connect(lambda: self.spectrum_view.setBackground("w")) - self.actionRed.triggered.connect(lambda: self.spectrum_view.setBackground("r")) - self.actionYellow.triggered.connect(lambda: self.spectrum_view.setBackground("y")) - self.actionBlue.triggered.connect(lambda: self.spectrum_view.setBackground("b")) - self.actionBlack.triggered.connect(lambda: self.spectrum_view.setBackground((0, 0, 0))) - - self.actn1.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn1.text()))) - self.actn2.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn2.text()))) - self.actn3.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn3.text()))) - self.actn4.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn4.text()))) - self.actn5.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn5.text()))) - self.actn6.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn6.text()))) - self.actn8.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn8.text()))) - self.actn10.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn10.text()))) - - self.actionOpen_Image_Data.triggered.connect(self.browse_file) - self.actionOpen_Multiple_Files.triggered.connect(self.createVirtualStack) - self.actionSave_as.triggered.connect(lambda: self.save_stack()) - self.actionExit.triggered.connect(lambda: QApplication.closeAllWindows()) - self.actionOpen_in_GitHub.triggered.connect(self.open_github_link) - self.actionLoad_Energy.triggered.connect(self.select_elist) - self.menuFile.setToolTipsVisible(True) - - # Accessories - self.actionOpen_Mask_Gen.triggered.connect(self.openMaskMaker) - self.actionMultiColor.triggered.connect(self.openMultiColorWindow) - - # calculations - self.pb_transpose_stack.clicked.connect(lambda: self.threadMaker(self.transposeStack)) - self.pb_swapXY_stack.clicked.connect(lambda: self.threadMaker(self.swapStackXY)) - self.pb_reset_img.clicked.connect(self.reloadImageStack) - self.pb_crop.clicked.connect(self.crop_to_dim) - self.pb_crop.clicked.connect(self.view_stack) - self.sb_scaling_factor.valueChanged.connect(self.view_stack) - self.pb_ref_xanes.clicked.connect(self.select_ref_file) - self.pb_elist_xanes.clicked.connect(self.select_elist) - - # batchjobs - self.actionPlotAllCorrelations.triggered.connect(self.plotCorrelationsAllCombinations) - - [ - uis.valueChanged.connect(self.replot_image) - for uis in [self.hs_smooth_size, self.hs_nsigma, self.hs_bg_threshold] - ] - - [ - uis.stateChanged.connect(self.replot_image) - for uis in [self.cb_remove_bg, self.cb_remove_outliers, self.cb_smooth, self.cb_norm, self.cb_log] - ] - - [ - uis.stateChanged.connect(self.view_stack) - for uis in [self.cb_remove_edges, self.cb_upscale, self.cb_rebin] - ] - - # ToolBar - self.actionStack_Info.triggered.connect(self.displayStackInfo) - self.actionSave_Image.triggered.connect(self.save_disp_img) - self.actionExport_Stack.triggered.connect(lambda: self.save_stack()) - - # ROI background - self.actionSubtract_ROI_BG.triggered.connect(lambda: self.threadMaker(self.removeROIBGStack)) - - # alignment - self.pb_load_align_ref.clicked.connect(self.loadAlignRefImage) - self.pb_loadAlignTranform.clicked.connect(self.importAlignTransformation) - self.pb_saveAlignTranform.clicked.connect(self.exportAlignTransformation) - self.pb_alignStack.clicked.connect(lambda: self.threadMaker(self.stackRegistration)) - # self.pb_alignStack.clicked.connect(self.stackRegistration) - - # save_options - self.actionSave_Sum_Image.triggered.connect(lambda: self.save_stack(method="sum")) - self.actionSave_Mean_Image.triggered.connect(lambda: self.save_stack(method="mean")) - self.pb_save_disp_spec.clicked.connect(self.save_disp_spec) - self.actionSave_Energy_List.triggered.connect(self.saveEnergyList) - self.pb_show_roi.clicked.connect(self.getROIMask) - self.pb_addToCollector.clicked.connect(self.addSpectrumToCollector) - self.pb_collect_clear.clicked.connect(lambda: self.spectrum_view_collect.clear()) - self.pb_saveCollectorPlot.clicked.connect(self.saveCollectorPlot) - - # XANES Normalization - self.pb_apply_xanes_norm.clicked.connect(self.nomalizeLiveSpec) - self.pb_auto_Eo.clicked.connect(self.findEo) - self.pb_xanes_norm_vals.clicked.connect(self.initNormVals) - self.pb_apply_norm_to_stack.clicked.connect(lambda: self.threadMaker(self.normalizeStack)) - self.actionExport_Norm_Params.triggered.connect(self.exportNormParams) - self.actionImport_Norm_Params.triggered.connect(self.importNormParams) - - # Analysis - self.pb_pca_scree.clicked.connect(self.pca_scree_) - self.pb_calc_components.clicked.connect(self.calc_comp_) - self.pb_kmeans_elbow.clicked.connect(self.kmeans_elbow) - self.pb_calc_cluster.clicked.connect(self.clustering_) - self.pb_xanes_fit.clicked.connect(self.fast_xanes_fitting) - self.pb_plot_refs.clicked.connect(self.plt_xanes_refs) - - self.show() - - self.threadpool = QThreadPool() - print(f"Multithreading with maximum {self.threadpool.maxThreadCount()} threads") - - # View Options - def darkMode(self): - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/darkStyle.css")).read()) - - def defaultMode(self): - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - - def modernMode(self): - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/modern.css")).read()) - - def setPlotLineWidth(self, width_input): - self.plotWidth = width_input - try: - self.update_spectrum() - except Exception: - pass - - def openMultiColorWindow(self): - self.multicolorwindow = MultiChannelWindow() - self.multicolorwindow.show() - - def openMaskMaker(self): - self.mask_window = MaskSpecViewer(xanes_stack=self.displayedStack, energy=self.energy) - self.mask_window.show() - - def open_github_link(self): - webbrowser.open("https://github.com/pattammattel/NSLS-II-MIDAS/wiki") - - def threadMaker(self, funct): - # Pass the function to execute - worker = Worker(funct) # Any other args, kwargs are passed to the run function - self.loadSplashScreen() - worker.signals.start.connect(self.splash.startAnimation) - worker.signals.result.connect(self.print_output) - - list( - map( - worker.signals.finished.connect, - [ - self.thread_complete, - self.splash.stopAnimation, - self.update_stack_info, - self.update_spectrum, - self.update_image_roi, - ], - ) - ) - - # Execute - self.threadpool.start(worker) - - # File Loading - - def createVirtualStack(self): - """User can load multiple/series of tiff images with same shape. - The 'self.load_stack()' recognizes 'self.filename as list and create the stack. - """ - self.energy = [] - filter = "TIFF (*.tiff);;TIF (*.tif);;all_files (*)" - file_name = QFileDialog() - file_name.setFileMode(QFileDialog.ExistingFiles) - names = file_name.getOpenFileNames(self, "Open files", " ", filter) - if names[0]: - - self.file_name = names[0] - self.load_stack() - - else: - self.statusbar_main.showMessage("No file has selected") - pass - - def load_stack(self): - - """load the image data from the selected file. - If the the choice is for multiple files stack will be created in a loop. - If single h5 file is selected the unpacking will be done with 'get_xrf_data' function in StackCalcs. - From the h5 the program can recognize the beamline. The exported stack will be normalized to I0. - - If the single tiff file is choosen tf.imread() is used. - - The output 'self.im_stack' is the unmodified data file - """ - - self.log_warning = False # for the Qmessage box in cb_log - self.image_roi2_flag = False - self.cb_log.setChecked(False) - self.cb_remove_edges.setChecked(False) - self.cb_norm.setChecked(False) - self.cb_smooth.setChecked(False) - self.cb_remove_outliers.setChecked(False) - self.cb_remove_bg.setChecked(False) - self.cb_rebin.setChecked(False) - self.cb_upscale.setChecked(False) - self.sb_xrange1.setValue(0) - self.sb_yrange1.setValue(0) - self.sb_zrange1.setValue(0) - - self.menuMask.setEnabled(True) - self.actionLoad_Energy.setEnabled(True) - self.actionSave_Energy_List.setEnabled(True) - self.actionSave_as.setEnabled(True) - - self.sb_zrange2.setMaximum(99999) - self.sb_xrange2.setMaximum(99999) - self.sb_yrange2.setMaximum(99999) - - self.statusbar_main.showMessage("Loading.. please wait...") - - if isinstance(self.file_name, list): - - all_images = [] - - for im_file in self.file_name: - img = tf.imread(im_file) - all_images.append(img) # row major image - self.im_stack = np.dstack(all_images).transpose((2, 0, 1)) - self.avgIo = 1 # I0 is only applicable to XRF h5 files - self.sb_zrange2.setValue(self.im_stack.shape[0]) - - else: - - if self.file_name.endswith(".h5"): - self.im_stack, mono_e, bl_name, self.avgIo = get_xrf_data(self.file_name) - self.statusbar_main.showMessage(f"Data from {bl_name}") - self.sb_zrange2.setValue(mono_e / 10) - self.energy = [] - - elif self.file_name.endswith(".tiff") or self.file_name.endswith(".tif"): - self.im_stack_ = tf.imread(self.file_name) - if self.im_stack_.ndim == 2: - self.im_stack = self.im_stack_.reshape(1, self.im_stack_.shape[0], self.im_stack_.shape[1]) - - else: - self.im_stack = self.im_stack_ - self.sb_zrange2.setValue(self.im_stack.shape[0]) - self.autoEnergyLoader() - self.energyUnitCheck() - self.avgIo = 1 - - else: - logger.error("Unknown data format") - - """ Fill the stack dimensions to the GUI and set the image dimensions as max values. - This prevent user from choosing higher image dimensions during a resizing event""" - - logger.info(f" loaded stack with {np.shape(self.im_stack)} from the file") - - try: - logger.info(f" Transposed to shape: {np.shape(self.im_stack)}") - self.init_dimZ, self.init_dimY, self.init_dimX = self.im_stack.shape - # Remove any previously set max value during a reload - - self.sb_xrange2.setValue(self.init_dimX) - self.sb_yrange2.setValue(self.init_dimY) - - except UnboundLocalError: - logger.error("No file selected") - pass - - self.view_stack() - logger.info("Stack displayed correctly") - self.update_stack_info() - - logger.info(f"completed image shape {np.shape(self.im_stack)}") - - try: - self.statusbar_main.showMessage(f"Loaded: {self.file_name}") - - except AttributeError: - self.statusbar_main.showMessage("New Stack is made from selected tiffs") - pass - - def browse_file(self): - """To open a file widow and choose the data file. - The filename will be used to load data using 'rest and load stack' function""" - - filename = QFileDialog().getOpenFileName( - self, "Select image data", "", "image file(*.hdf *.h5 *tiff *tif )" - ) - self.file_name = str(filename[0]) - - # if user decides to cancel the file window gui returns to original state - if self.file_name: - self.disconnectImageActions() - self.isAReload = False - self.load_stack() - - else: - self.statusbar_main.showMessage("No file has selected") - pass - - def autoEnergyLoader(self): - - dir_, filename_ = os.path.split(self.file_name) - self.efilePath_name = os.path.join(dir_, os.path.splitext(filename_)[0] + ".txt") - self.efilePath_log = os.path.join(dir_, "maps_log_tiff.txt") - - if os.path.isfile(self.efilePath_name): - self.efilePath = self.efilePath_name - self.efileLoader() - self.statusbar_main.showMessage(f"Energy File detected {self.efilePath}") - - elif os.path.isfile(self.efilePath_log): - self.efilePath = self.efilePath_log - self.efileLoader() - self.statusbar_main.showMessage(f"Energy File detected {self.efilePath}") - - else: - self.efilePath = False - self.efileLoader() - - def update_stack_info(self): - z, y, x = np.shape(self.displayedStack) - self.sb_zrange2.setMaximum(z + self.sb_zrange1.value()) - self.sb_xrange2.setValue(x) - self.sb_xrange2.setMaximum(x) - self.sb_yrange2.setValue(y) - self.sb_yrange2.setMaximum(y) - logger.info("Stack info has been updated") - - # Image Transformations - - def crop_to_dim(self): - self.x1, self.x2 = self.sb_xrange1.value(), self.sb_xrange2.value() - self.y1, self.y2 = self.sb_yrange1.value(), self.sb_yrange2.value() - self.z1, self.z2 = self.sb_zrange1.value(), self.sb_zrange2.value() - - try: - self.displayedStack = remove_nan_inf( - self.displayedStack[self.z1 : self.z2, self.y1 : self.y2, self.x1 : self.x2] - ) - except Exception: - self.displayedStack = remove_nan_inf( - self.im_stack[self.z1 : self.z2, self.y1 : self.y2, self.x1 : self.x2] - ) - - def transpose_stack(self): - self.displayedStack = self.displayedStack.T - self.update_spectrum() - self.update_spec_image_roi() - - # Alignement - - def loadAlignRefImage(self): - filename = QFileDialog().getOpenFileName(self, "Image Data", "", "*.tiff *.tif") - file_name = str(filename[0]) - self.alignRefImage = tf.imread(file_name) - assert self.alignRefImage.shape == self.displayedStack.shape, "Image dimensions do not match" - self.refStackAvailable = True - self.rb_alignRefVoid.setChecked(False) - self.change_color_on_load(self.pb_load_align_ref) - - def stackRegistration(self): - - self.transformations = { - "TRANSLATION": StackReg.TRANSLATION, - "RIGID_BODY": StackReg.RIGID_BODY, - "SCALED_ROTATION": StackReg.SCALED_ROTATION, - "AFFINE": StackReg.AFFINE, - "BILINEAR": StackReg.BILINEAR, - } - - self.transformType = self.transformations[self.cb_alignTransform.currentText()] - self.alignReferenceImage = self.cb_alignRef.currentText() - self.alignRefStackVoid = self.rb_alignRefVoid.isChecked() - self.alignMaxIter = self.sb_maxIterVal.value() - - if self.cb_use_tmatFile.isChecked(): - - if len(self.loaded_tranform_file) > 0: - - self.displayedStack = align_with_tmat( - self.displayedStack, tmat_file=self.loaded_tranform_file, transformation=self.transformType - ) - logger.info("Aligned to the tranform File") - - else: - logger.error("No Tranformation File Loaded") - - elif self.cb_iterAlign.isChecked(): - - if not self.refStackAvailable: - self.alignRefImage = self.displayedStack - else: - pass - - self.displayedStack = align_stack_iter( - self.displayedStack, - ref_stack_void=False, - ref_stack=self.alignRefImage, - transformation=self.transformType, - method=("previous", "first"), - max_iter=self.alignMaxIter, - ) - - else: - if not self.refStackAvailable: - self.alignRefImage = self.displayedStack - - else: - pass - - self.displayedStack, self.tranform_file = align_stack( - self.displayedStack, - ref_image_void=True, - ref_stack=self.alignRefImage, - transformation=self.transformType, - reference=self.alignReferenceImage, - ) - logger.info("New Tranformation file available") - self.im_stack = self.displayedStack - - def exportAlignTransformation(self): - file_name = QFileDialog().getSaveFileName( - self, "Save Transformation File", "TranformationMatrix.npy", "text file (*.npy)" - ) - if file_name[0]: - np.save(file_name[0], self.tranform_file) - else: - pass - - def importAlignTransformation(self): - file_name = QFileDialog().getOpenFileName(self, "Open Transformation File", " ", "text file (*.npy)") - if file_name[0]: - self.loaded_tranform_file = np.load(file_name[0]) - self.cb_use_tmatFile.setChecked(True) - logger.info("Tranformation File Loaded") - else: - pass - - def loadSplashScreen(self): - self.splash = LoadingScreen() - - px = self.geometry().x() - py = self.geometry().y() - ph = self.geometry().height() - pw = self.geometry().width() - dw = self.splash.width() - dh = self.splash.height() - new_x, new_y = px + (0.5 * pw) - dw, py + (0.5 * ph) - dh - self.splash.setGeometry(new_x, new_y, dw, dh) - - self.splash.show() - - def reloadImageStack(self): - self.isAReload = True - self.load_stack() - - def update_stack(self): - self.displayedStack = self.im_stack - self.crop_to_dim() - - if self.cb_rebin.isChecked(): - self.cb_upscale.setChecked(False) - self.sb_scaling_factor.setEnabled(True) - self.displayedStack = resize_stack(self.displayedStack, scaling_factor=self.sb_scaling_factor.value()) - self.update_stack_info() - - elif self.cb_upscale.isChecked(): - self.cb_rebin.setChecked(False) - self.sb_scaling_factor.setEnabled(True) - self.displayedStack = resize_stack( - self.displayedStack, upscaling=True, scaling_factor=self.sb_scaling_factor.value() - ) - self.update_stack_info() - - if self.cb_remove_outliers.isChecked(): - self.hs_nsigma.setEnabled(True) - nsigma = self.hs_nsigma.value() / 10 - self.displayedStack = remove_hot_pixels(self.displayedStack, NSigma=nsigma) - self.label_nsigma.setText(str(nsigma)) - logger.info(f"Removing Outliers with NSigma {nsigma}") - - elif self.cb_remove_outliers.isChecked() is False: - self.hs_nsigma.setEnabled(False) - - if self.cb_remove_edges.isChecked(): - self.displayedStack = remove_edges(self.displayedStack) - logger.info(f"Removed edges, new shape {self.displayedStack.shape}") - self.update_stack_info() - - if self.cb_remove_bg.isChecked(): - self.hs_bg_threshold.setEnabled(True) - logger.info("Removing background") - bg_threshold = self.hs_bg_threshold.value() - self.label_bg_threshold.setText(str(bg_threshold) + "%") - self.displayedStack = clean_stack(self.displayedStack, auto_bg=False, bg_percentage=bg_threshold) - - elif self.cb_remove_bg.isChecked() is False: - self.hs_bg_threshold.setEnabled(False) - - if self.cb_log.isChecked(): - - self.displayedStack = remove_nan_inf(np.log10(self.displayedStack)) - logger.info("Log Stack is in use") - - if self.cb_smooth.isChecked(): - self.hs_smooth_size.setEnabled(True) - window = self.hs_smooth_size.value() - if window % 2 == 0: - window = +1 - self.smooth_winow_size.setText("Window size: " + str(window)) - self.displayedStack = smoothen(self.displayedStack, w_size=window) - logger.info("Spectrum Smoothening Applied") - - elif self.cb_smooth.isChecked() is False: - self.hs_smooth_size.setEnabled(False) - - if self.cb_norm.isChecked(): - logger.info("Normalizing spectra") - self.displayedStack = normalize(self.displayedStack, norm_point=-1) - - logger.info("Updated image is in use") - - # ImageView - - def view_stack(self): - - if not self.im_stack.ndim == 3: - raise ValueError("stack should be an ndarray with ndim == 3") - else: - self.update_stack() - # self.StackUpdateThread() - - try: - self.image_view.removeItem(self.image_roi_math) - except Exception: - pass - - (self.dim1, self.dim2, self.dim3) = self.displayedStack.shape - self.image_view.setImage(self.displayedStack) - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - self.image_view.setPredefinedGradient("viridis") - self.image_view.setCurrentIndex(self.dim1 // 2) - if len(self.energy) == 0: - self.energy = np.arange(self.z1, self.z2) * 10 - logger.info("Arbitary X-axis used in the plot for XANES") - self.sz = np.max( - [int(self.dim2 * 0.1), int(self.dim3 * 0.1)] - ) # size of the roi set to be 10% of the image area - - self.stack_center = self.energy[len(self.energy) // 2] - self.stack_width = (self.energy.max() - self.energy.min()) // 10 - self.spec_roi = pg.LinearRegionItem( - values=(self.stack_center - self.stack_width, self.stack_center + self.stack_width) - ) - - # a second optional ROI for calculations follow - self.image_roi_math = pg.PolyLineROI( - [[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], - pos=(int(self.dim3 // 3), int(self.dim2 // 3)), - pen="r", - closed=True, - removable=True, - ) - - self.spec_roi_math = pg.LinearRegionItem( - values=(self.stack_center - self.stack_width - 10, self.stack_center + self.stack_width - 10), - pen="r", - brush=QtGui.QColor(0, 255, 200, 50), - ) - self.spec_lo_m_idx = self.spec_hi_m_idx = 0 - - self.setImageROI() - self.update_spectrum() - self.update_image_roi() - - if not self.isAReload: - # image connections - self.image_view.mousePressEvent = self.getPointSpectrum - self.spec_roi.sigRegionChanged.connect(self.update_image_roi) - self.spec_roi_math.sigRegionChangeFinished.connect(self.spec_roi_calc) - self.pb_apply_spec_calc.clicked.connect(self.spec_roi_calc) - self.rb_math_roi.clicked.connect(self.update_spectrum) - self.pb_add_roi_2.clicked.connect(self.math_img_roi_flag) - self.image_roi_math.sigRegionChangeFinished.connect(self.image_roi_calc) - self.pb_apply_img_calc.clicked.connect(self.image_roi_calc) - - [ - rbs.clicked.connect(self.setImageROI) - for rbs in [self.rb_poly_roi, self.rb_elli_roi, self.rb_rect_roi, self.rb_line_roi, self.rb_circle_roi] - ] - - def disconnectImageActions(self): - for btns in [self.pb_apply_spec_calc, self.rb_math_roi, self.pb_add_roi_2, self.pb_apply_img_calc]: - try: - btns.disconnect() - except Exception: - pass - - def select_elist(self): - self.energyFileChooser() - self.efileLoader() - self.energyUnitCheck() - self.view_stack() - - def efileLoader(self): - - if self.efilePath: - - if str(self.efilePath).endswith("log_tiff.txt"): - self.energy = energy_from_logfile(logfile=str(self.efilePath)) - logger.info("Log file from pyxrf processing") - - else: - self.energy = np.loadtxt(str(self.efilePath)) - self.change_color_on_load(self.pb_elist_xanes) - logger.info("Energy file loaded") - - else: - self.statusbar_main.showMessage("No Energy List Selected, Setting Arbitary Axis") - self.energy = np.arange(self.im_stack.shape[0]) - logger.info("Arbitary Energy Axis") - - # assert len(self.energy) == self.dim1, "Number of Energy Points is not equal to stack length" - - def energyUnitCheck(self): - - if np.max(self.energy) < 100: - self.cb_kev_flag.setChecked(True) - self.energy *= 1000 - - else: - self.cb_kev_flag.setChecked(False) - - def select_ref_file(self): - self.pb_xanes_fit.setEnabled(True) - self.ref_names = [] - file_name = QFileDialog().getOpenFileName(self, "Open reference file", "", "text file (*.txt *.nor)") - if file_name[0]: - if file_name[0].endswith(".nor"): - self.refs, self.ref_names = create_df_from_nor_try2(athenafile=str(file_name[0])) - self.change_color_on_load(self.pb_ref_xanes) - - elif file_name[0].endswith(".txt"): - self.refs = pd.read_csv(str(file_name[0]), header=None, delim_whitespace=True) - self.change_color_on_load(self.pb_ref_xanes) - - else: - logger.error("No file selected") - pass - - self.plt_xanes_refs() - - def plt_xanes_refs(self): - - try: - self.ref_plot.close() - except Exception: - pass - - self.ref_plot = plot(title="Reference Standards") - self.ref_plot.setLabel("bottom", "Energy") - self.ref_plot.setLabel("left", "Intensity") - self.ref_plot.addLegend() - - for n in range(np.shape(self.refs)[1]): - - if not n == 0: - self.ref_plot.plot( - self.refs.values[:, 0], - self.refs.values[:, n], - pen=pg.mkPen(self.plt_colors[n - 1], width=self.plotWidth), - name=self.ref_names[n], - ) - - def getPointSpectrum(self, event): - if event.type() == QtCore.QEvent.MouseButtonDblClick: - if event.button() == QtCore.Qt.LeftButton: - self.xpixel = int(self.image_view.view.mapSceneToView(event.pos()).x()) - 1 - zlim, ylim, xlim = self.displayedStack.shape - - if self.xpixel > xlim: - self.xpixel = xlim - 1 - - self.ypixel = int(self.image_view.view.mapSceneToView(event.pos()).y()) - 1 - if self.ypixel > ylim: - self.ypixel = ylim - 1 - self.spectrum_view.addLegend() - self.point_spectrum = self.displayedStack[:, self.ypixel, self.xpixel] - self.spectrum_view.plot( - self.xdata, - self.point_spectrum, - clear=True, - pen=pg.mkPen(pg.mkColor(0, 0, 255, 255), width=self.plotWidth), - symbol="o", - symbolSize=6, - symbolBrush="r", - name=f"Point Spectrum; x= {self.xpixel}, y= {self.ypixel}", - ) - - self.spectrum_view.addItem(self.spec_roi) - - self.statusbar_main.showMessage(f"{self.xpixel} and {self.ypixel}") - - def setImageROI(self): - - self.lineROI = pg.LineSegmentROI([[int(self.dim3 // 2), int(self.dim2 // 2)], [self.sz, self.sz]], pen="r") - - self.rectROI = pg.RectROI( - [int(self.dim3 // 2), int(self.dim2 // 2)], - [self.sz, self.sz], - pen="w", - maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), - ) - - self.rectROI.addTranslateHandle([0, 0], [2, 2]) - self.rectROI.addRotateHandle([0, 1], [2, 2]) - - self.ellipseROI = pg.EllipseROI( - [int(self.dim3 // 2), int(self.dim2 // 2)], - [self.sz, self.sz], - pen="w", - maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), - ) - - self.circleROI = pg.CircleROI( - [int(self.dim3 // 2), int(self.dim2 // 2)], - [self.sz, self.sz], - pen="w", - maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), - ) # pos and size - - self.polyLineROI = pg.PolyLineROI( - [[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], - pos=(int(self.dim3 // 2), int(self.dim2 // 2)), - maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), - closed=True, - removable=True, - ) - - self.rois = { - "rb_line_roi": self.lineROI, - "rb_rect_roi": self.rectROI, - "rb_circle_roi": self.circleROI, - "rb_elli_roi": self.ellipseROI, - "rb_poly_roi": self.polyLineROI, - } - - button_name = self.sender() - - if button_name.objectName() in self.rois.keys(): - self.roi_preference = button_name.objectName() - - else: - self.roi_preference = "rb_rect_roi" # default - - try: - self.image_view.removeItem(self.image_roi) - - except Exception: - pass - - # ROI settings for image, used polyline roi with non rectangular shape - - self.image_roi = self.rois[self.roi_preference] - self.image_view.addItem(self.image_roi) - self.image_roi.sigRegionChanged.connect(self.update_spectrum) - - def replot_image(self): - self.update_stack() - self.update_spectrum() - self.update_image_roi() - - def update_spec_roi_values(self): - self.stack_center = int(self.energy[len(self.energy) // 2]) - self.stack_width = int((self.energy.max() - self.energy.min()) * 0.05) - self.spec_roi.setBounds([self.xdata[0], self.xdata[-1]]) # if want to set bounds for the spec roi - self.spec_roi_math.setBounds([self.xdata[0], self.xdata[-1]]) - - def update_spectrum(self): - - # set x-axis values; array taken from energy values, if clipped z box values will update the array - self.xdata = self.energy[self.sb_zrange1.value() : self.sb_zrange2.value()] - - # get the cropped stack from ROI region; pyqtgraph function is used - self.roi_img_stk = self.image_roi.getArrayRegion( - self.displayedStack, self.image_view.imageItem, axes=(1, 2) - ) - - posx, posy = self.image_roi.pos() - self.le_roi.setText(str(int(posx)) + ":" + str(int(posy))) - - # display the ROI features in the line edit boxes - if self.roi_img_stk.ndim == 3: - sizex, sizey = self.roi_img_stk.shape[1], self.roi_img_stk.shape[2] - self.le_roi_size.setText(str(sizex) + "," + str(sizey)) - self.mean_spectra = get_mean_spectra(self.roi_img_stk) - - elif self.roi_img_stk.ndim == 2: - sizex, sizey = self.roi_img_stk.shape[0], self.roi_img_stk.shape[1] - self.le_roi_size.setText(str(sizex) + "," + str(sizey)) - self.mean_spectra = self.roi_img_stk.mean(-1) - - self.spectrum_view.addLegend() - - try: - self.spectrum_view.plot( - self.xdata, - self.mean_spectra, - pen=pg.mkPen(pg.mkColor(5, 255, 5, 255), width=self.plotWidth), - clear=True, - symbol="o", - symbolSize=6, - symbolBrush="r", - name="ROI Spectrum", - ) - except Exception: - self.spectrum_view.plot( - self.mean_spectra, - clear=True, - pen=pg.mkPen(pg.mkColor(5, 255, 5, 255), width=self.plotWidth), - symbol="o", - symbolSize=6, - symbolBrush="r", - name="ROI Spectrum", - ) - - if self.energy[-1] > 1000: - self.e_unit = "eV" - else: - self.e_unit = "keV" - - self.spectrum_view.setLabel("bottom", "Energy", self.e_unit) - self.spectrum_view.setLabel("left", "Intensity", "A.U.") - self.spectrum_view.addItem(self.spec_roi) - self.update_spec_roi_values() - self.math_roi_flag() - - def update_image_roi(self): - self.spec_lo, self.spec_hi = self.spec_roi.getRegion() - self.spec_lo_idx = (np.abs(self.energy - self.spec_lo)).argmin() - self.spec_hi_idx = (np.abs(self.energy - self.spec_hi)).argmin() - self.le_spec_roi.setText(str(int(self.spec_lo)) + ":" + str(int(self.spec_hi))) - self.le_spec_roi_size.setText(str(int(self.spec_hi - self.spec_lo))) - self.update_spec_roi_values() - self.stackIndexToNames() - - try: - if int(self.spec_lo_idx) == int(self.spec_hi_idx): - self.disp_img = self.displayedStack[int(self.spec_hi_idx), :, :] - - else: - self.disp_img = self.displayedStack[int(self.spec_lo_idx) : int(self.spec_hi_idx), :, :].mean(0) - - self.image_view.setImage(self.disp_img) - self.statusbar_main.showMessage(f"Image Display is {self.corrImg1}") - except Exception: - logger.warning("Indices are out of range; Image cannot be created") - pass - - def set_spec_roi(self): - self.spec_lo_, self.spec_hi_ = int(self.sb_roi_spec_s.value()), int(self.sb_roi_spec_e.value()) - self.spec_lo_idx_ = (np.abs(self.energy - self.spec_lo_)).argmin() - self.spec_hi_idx_ = (np.abs(self.energy - self.spec_hi_)).argmin() - self.spec_roi.setRegion((self.xdata[self.spec_lo_idx_], self.xdata[self.spec_hi_idx_])) - self.update_image_roi() - - def math_roi_flag(self): - if self.rb_math_roi.isChecked(): - self.rb_math_roi.setStyleSheet("color : green") - self.spectrum_view.addItem(self.spec_roi_math) - else: - self.spectrum_view.removeItem(self.spec_roi_math) - - def spec_roi_calc(self): - - self.spec_lo_m, self.spec_hi_m = self.spec_roi_math.getRegion() - self.spec_lo_m_idx = (np.abs(self.energy - self.spec_lo_m)).argmin() - self.spec_hi_m_idx = (np.abs(self.energy - self.spec_hi_m)).argmin() - - if int(self.spec_lo_idx) == int(self.spec_hi_idx): - self.img1 = self.displayedStack[int(self.spec_hi_idx), :, :] - - else: - self.img1 = self.displayedStack[int(self.spec_lo_idx) : int(self.spec_hi_idx), :, :].mean(0) - - if int(self.spec_lo_m_idx) == int(self.spec_hi_m_idx): - self.img2 = self.displayedStack[int(self.spec_hi_m_idx), :, :] - - else: - self.img2 = self.displayedStack[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx), :, :].mean(0) - - if self.cb_roi_operation.currentText() == "Correlation Plot": - self.correlation_plot() - - else: - calc = {"Divide": np.divide, "Subtract": np.subtract, "Add": np.add} - self.disp_img = remove_nan_inf(calc[self.cb_roi_operation.currentText()](self.img1, self.img2)) - self.image_view.setImage(self.disp_img) - - def math_img_roi_flag(self): - - button_name = self.sender().text() - logger.info(f"{button_name}") - if button_name == "Add ROI_2": - self.image_view.addItem(self.image_roi_math) - self.pb_add_roi_2.setText("Remove ROI_2") - self.image_roi2_flag = 1 - elif button_name == "Remove ROI_2": - self.image_view.removeItem(self.image_roi_math) - self.pb_add_roi_2.setText("Add ROI_2") - self.image_roi2_flag = 0 - - else: - pass - logger.error("Unknown signal for second ROI") - - def image_roi_calc(self): - - if self.image_roi2_flag == 1: - self.calc = {"Divide": np.divide, "Subtract": np.subtract, "Add": np.add} - self.update_spec_image_roi() - else: - logger.error("No ROI2 found") - return - - def update_spec_image_roi(self): - - self.math_roi_reg = self.image_roi_math.getArrayRegion( - self.displayedStack, self.image_view.imageItem, axes=(1, 2) - ) - if self.math_roi_reg.ndim == 3: - - self.math_roi_spectra = get_mean_spectra(self.math_roi_reg) - - elif self.roi_img_stk.ndim == 2: - self.math_roi_spectra = self.math_roi_reg.mean(-1) - - if self.cb_img_roi_action.currentText() in self.calc.keys(): - - calc_spec = self.calc[self.cb_img_roi_action.currentText()](self.mean_spectra, self.math_roi_spectra) - self.spectrum_view.addLegend() - self.spectrum_view.plot( - self.xdata, - calc_spec, - clear=True, - pen=pg.mkPen("m", width=2), - name=self.cb_img_roi_action.currentText() + "ed", - ) - self.spectrum_view.plot(self.xdata, self.math_roi_spectra, pen=pg.mkPen("y", width=2), name="ROI2") - self.spectrum_view.plot(self.xdata, self.mean_spectra, pen=pg.mkPen("g", width=2), name="ROI1") - - elif self.cb_img_roi_action.currentText() == "Compare": - self.spectrum_view.plot( - self.xdata, self.math_roi_spectra, pen=pg.mkPen("y", width=2), clear=True, name="ROI2" - ) - self.spectrum_view.plot(self.xdata, self.mean_spectra, pen=pg.mkPen("g", width=2), name="ROI1") - - self.spectrum_view.addItem(self.spec_roi) - - def displayStackInfo(self): - - try: - - if isinstance(self.file_name, list): - info = f"Folder; {os.path.dirname(self.file_name[0])} \n" - for n, name in enumerate(self.file_name): - info += f"{n}: {os.path.basename(name)} \n" - - # info = f'Stack order; {[name for name in enumerate(self.file_name)]}' - else: - info = f"Stack; {self.file_name}" - - self.infoWindow = StackInfo(str(info)) - self.infoWindow.show() - - except AttributeError: - self.statusbar_main.showMessage("Warning: No Image Data Loaded") - - def stackIndexToNames(self): - # create list of tiff file names for virtutal stack for plot axes - self.elemFileName = [] - - if isinstance(self.file_name, list): - for name in self.file_name: - self.elemFileName.append(os.path.basename(name).split(".")[0]) - - logger.info(f" Virtual Stack - list of image names; {self.elemFileName}") - - # if the roi focus on one frame, Note that this slicing excludes the last index - if int(self.spec_lo_idx) == int(self.spec_hi_idx): - self.corrImg1 = str(self.elemFileName[int(self.spec_lo_idx)]) - else: - self.corrImg1 = self.elemFileName[int(self.spec_lo_idx) : int(self.spec_hi_idx)] - if len(self.corrImg1) > 1: - self.corrImg1 = f"Sum of {self.corrImg1} " - - if int(self.spec_lo_m_idx) == int(self.spec_hi_m_idx): - self.corrImg2 = str(self.elemFileName[int(self.spec_lo_m_idx)]) - - else: - self.corrImg2 = self.elemFileName[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx)] - - if len(self.corrImg2) > 1: - self.corrImg2 = f"Sum of {self.corrImg2}" - - logger.info( - f"Correlation stack {int(self.spec_lo_idx)}:{int(self.spec_hi_idx)} with " - f"{int(self.spec_lo_m_idx)}:{int(self.spec_hi_m_idx)}" - ) - - logger.info(f" Virtual Stack; corrlation plot of {self.corrImg1} vs {self.corrImg2}") - else: - self.corrImg1 = ( - f" Sum of {os.path.basename(self.file_name).split('.')[0]}_{int(self.spec_lo_idx)} " - f"to {int(self.spec_hi_idx)}" - ) - self.corrImg2 = ( - f" Sum of {os.path.basename(self.file_name).split('.')[0]}_{int(self.spec_lo_m_idx)} " - f"to {int(self.spec_hi_m_idx)}" - ) - # logger.info(f" corrlation plot of {self.corrImg1} vs {self.corrImg2}") - - def correlation_plot(self): - self.stackIndexToNames() - - self.statusbar_main.showMessage(f"Correlation of {self.corrImg1} with {self.corrImg2}") - - if self.rb_roiRegionOnly.isChecked(): - self.roi_mask = self.image_roi.getArrayRegion( - self.displayedStack, self.image_view.imageItem, axes=(1, 2) - ) - self.roi_img1 = np.mean(self.roi_mask[int(self.spec_lo_idx) : int(self.spec_hi_idx)], axis=0) - self.roi_img2 = np.mean(self.roi_mask[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx)], axis=0) - self.scatter_window = ScatterPlot( - self.roi_img1, self.roi_img2, (str(self.corrImg1), str(self.corrImg2)) - ) - - else: - - self.scatter_window = ScatterPlot(self.img1, self.img2, (str(self.corrImg1), str(self.corrImg2))) - - # ph = self.geometry().height() - # pw = self.geometry().width() - # px = self.geometry().x() - # py = self.geometry().y() - # dw = self.scatter_window.width() - # dh = self.scatter_window.height() - # self.scatter_window.setGeometry(px+0.65*pw, py + ph - 2*dh-5, dw, dh) - self.scatter_window.show() - - def plotCorrelationsAllCombinations(self): - - self.stackIndexToNames() - allElemCombNum = list(combinations(np.arange(len(self.elemFileName)), 2)) - - self.scW1 = self.scW2 = self.scW3 = self.scW4 = self.scW5 = None - self.scW6 = self.scW7 = self.scW8 = self.scW9 = self.scW10 = None - - self.scWindowList = [ - self.scW1, - self.scW2, - self.scW3, - self.scW4, - self.scW5, - self.scW6, - self.scW7, - self.scW8, - self.scW9, - self.scW10, - ] - self.scWindowDict = { - 1: self.scW1, - 2: self.scW2, - 3: self.scW3, - 4: self.scW4, - 5: self.scW5, - 6: self.scW6, - 7: self.scW7, - 8: self.scW8, - 9: self.scW9, - 10: self.scW10, - } - - if len(allElemCombNum) > len(self.scWindowDict): - - reply = QMessageBox.warning( - self, - "Plot Window Limit", - f"The number of combination exceeds " - f"maxiumum number of " - f"plot windows. First {len(self.scWindowDict)} " - f"combinations will be plotted. \n Proceed?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No, - ) - - if reply == QMessageBox.Yes: - - for i, pair in enumerate(allElemCombNum): - im1 = self.displayedStack[pair[0]] - im2 = self.displayedStack[pair[1]] - im1Name = self.elemFileName[pair[0]] - im2Name = self.elemFileName[pair[1]] - - self.scWindowDict[i] = ScatterPlot(im1, im2, (str(im1Name), str(im2Name))) - self.scWindowDict[i].show() - - if reply == QMessageBox.No: - return - - def getROIMask(self): - self.roi_mask = self.image_roi.getArrayRegion(self.displayedStack, self.image_view.imageItem, axes=(1, 2)) - self.newWindow = singleStackViewer(self.roi_mask) - self.newWindow.show() - - def save_stack(self, method="raw"): - - # self.update_stack() - file_name = QFileDialog().getSaveFileName( - self, "Save image data", "image_data.tiff", "image file(*tiff *tif )" - ) - if file_name[0]: - if method == "raw": - - tf.imsave(str(file_name[0]), self.displayedStack) - logger.info(f"Updated Image Saved: {str(file_name[0])}") - self.statusbar_main.showMessage(f"Updated Image Saved: {str(file_name[0])}") - elif method == "sum": - tf.imsave(str(file_name[0]), np.sum(self.displayedStack, axis=0)) - - elif method == "mean": - tf.imsave(str(file_name[0]), np.mean(self.displayedStack, axis=0)) - - else: - self.statusbar_main.showMessage("Saving cancelled") - logger.info(f"Save failed: {str(file_name[0])}") - pass - - def save_disp_img(self): - file_name = QFileDialog().getSaveFileName(self, "Save image data", "image.tiff", "image file(*tiff *tif )") - if file_name[0]: - tf.imsave(str(file_name[0]), self.disp_img) - self.statusbar_main.showMessage(f"Image Saved to {str(file_name[0])}") - logger.info(f"Updated Image Saved: {str(file_name[0])}") - - else: - logger.error("No file to save") - self.statusbar_main.showMessage("Saving cancelled") - pass - - def getLivePlotData(self): - try: - - data = np.squeeze([c.getData() for c in self.spectrum_view.plotItem.curves]) - # print(np.shape(data)) - if data.ndim == 2: - self.mu_ = data[1] - self.e_ = data[0] - elif data.ndim == 3: - e_mu = data[0, :, :] - self.mu_ = e_mu[1] - self.e_ = e_mu[0] - - else: - logger.error(f" Data shape of {data.ndim} is not supported") - pass - except AttributeError: - logger.error("No data loaded") - pass - - def addSpectrumToCollector(self): - self.getLivePlotData() - self.spectrum_view_collect.plot(self.e_, self.mu_, name="ROI Spectrum") - self.spectrum_view_collect.setLabel("bottom", "Energy", self.e_unit) - self.spectrum_view_collect.setLabel("left", "Intensity", "A.U.") - - def findEo(self): - try: - self.getLivePlotData() - e0_init = self.e_[np.argmax(np.gradient(self.mu_))] - self.dsb_norm_Eo.setValue(e0_init) - - except AttributeError: - logger.error("No data loaded") - pass - - def initNormVals(self): - self.getLivePlotData() - e0_init = self.e_[np.argmax(np.gradient(self.mu_))] - pre1, pre2, post1, post2 = xanesNormalization( - self.e_, self.mu_, e0=e0_init, step=None, nnorm=1, nvict=0, guess=True - ) - self.dsb_norm_pre1.setValue(pre1) - self.dsb_norm_pre2.setValue(pre2) - self.dsb_norm_post1.setValue(post1) - self.dsb_norm_post2.setValue(post2) - self.dsb_norm_Eo.setValue(e0_init) - - def getNormParams(self): - self.getLivePlotData() - eo_ = self.dsb_norm_Eo.value() - pre1_, pre2_ = self.dsb_norm_pre1.value(), self.dsb_norm_pre2.value() - norm1_, norm2_ = self.dsb_norm_post1.value(), self.dsb_norm_post2.value() - norm_order = self.sb_norm_order.value() - - return eo_, pre1_, pre2_, norm1_, norm2_, norm_order - - def exportNormParams(self): - self.xanesNormParam = {} - eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() - self.xanesNormParam["E0"] = eo_ - self.xanesNormParam["pre1"] = pre1_ - self.xanesNormParam["pre2"] = pre2_ - self.xanesNormParam["post1"] = norm1_ - self.xanesNormParam["post2"] = norm2_ - self.xanesNormParam["norm_order"] = norm_order - - file_name = QtWidgets.QFileDialog().getSaveFileName( - self, "Save XANES Norm Params", "xanes_norm_params.csv", "csv file(*csv)" - ) - - if file_name[0]: - - pd.DataFrame(self.xanesNormParam, index=[0]).to_csv(file_name[0]) - - else: - pass - - def importNormParams(self): - - file_name = QtWidgets.QFileDialog().getOpenFileName( - self, "Open a XANES Norm File", "", "csv file(*csv);;all_files (*)" - ) - - if file_name[0]: - xanesNormParam = pd.read_csv(file_name[0]) - self.dsb_norm_Eo.setValue(xanesNormParam["E0"]) - self.dsb_norm_pre1.setValue(xanesNormParam["pre1"]) - self.dsb_norm_pre2.setValue(xanesNormParam["pre2"]) - self.dsb_norm_post1.setValue(xanesNormParam["post1"]) - self.dsb_norm_post2.setValue(xanesNormParam["post2"]) - self.sb_norm_order.setValue(xanesNormParam["norm_order"]) - - def nomalizeLiveSpec(self): - eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() - self.spectrum_view.clear() - - pre_line, post_line, normXANES = xanesNormalization( - self.e_, - self.mu_, - e0=eo_, - step=None, - nnorm=norm_order, - nvict=0, - pre1=pre1_, - pre2=pre2_, - norm1=norm1_, - norm2=norm2_, - ) - - names = np.array(("Spectrum", "Pre", "Post")) - data_array = np.array((self.mu_, pre_line, post_line)) - colors = np.array(("c", "r", "m")) - - for data, clr, name in zip(data_array, colors, names): - self.spectrum_view.plot(self.e_, data, pen=pg.mkPen(clr, width=self.plotWidth), name=name) - - self.spectrum_view_norm.plot( - self.e_, normXANES, clear=True, pen=pg.mkPen(self.plt_colors[-1], width=self.plotWidth) - ) - self.spectrum_view_norm.setLabel("bottom", "Energy", self.e_unit) - self.spectrum_view_norm.setLabel("left", "Norm. Intensity", "A.U.") - - def normalizeStack(self): - self.getLivePlotData() - eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() - - self.im_stack = self.displayedStack = xanesNormStack( - self.e_, - self.displayedStack, - e0=eo_, - step=None, - nnorm=norm_order, - nvict=0, - pre1=pre1_, - pre2=pre2_, - norm1=norm1_, - norm2=norm2_, - ) - # self.im_stack = self.displayedStack - - def transposeStack(self): - self.im_stack = self.displayedStack = np.transpose(self.displayedStack, (2, 1, 0)) - - def swapStackXY(self): - self.im_stack = self.displayedStack = np.transpose(self.displayedStack, (0, 2, 1)) - - def removeROIBGStack(self): - self.displayedStack = subtractBackground(self.displayedStack, self.mean_spectra) - - def resetCollectorSpec(self): - pass - - def saveCollectorPlot(self): - exporter = pg.exporters.CSVExporter(self.spectrum_view_collect.plotItem) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName(self, "save spectra", "", "spectra (*csv)") - if file_name[0]: - exporter.export(str(file_name[0]) + ".csv") - else: - self.statusbar_main.showMessage("Saving cancelled") - pass - - def save_disp_spec(self): - - exporter = pg.exporters.CSVExporter(self.spectrum_view.plotItem) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName(self, "save spectrum", "", "spectra (*csv)") - if file_name[0]: - exporter.export(str(file_name[0]) + ".csv") - else: - self.statusbar_main.showMessage("Saving cancelled") - pass - - def saveEnergyList(self): - file_name = QFileDialog().getSaveFileName(self, "save energy list", "energy_list.txt", "text file (*txt)") - if file_name[0]: - np.savetxt(file_name[0], self.xdata, fmt="%.4f") - else: - pass - - def pca_scree_(self): - logger.info("Process started..") - self.update_stack() - pca_scree(self.displayedStack) - logger.info("Process complete") - - def calc_comp_(self): - - logger.info("Process started..") - - # self.update_stack() - n_components = self.sb_ncomp.value() - method_ = self.cb_comp_method.currentText() - - ims, comp_spec, decon_spec, decomp_map = decompose_stack( - self.displayedStack, decompose_method=method_, n_components_=n_components - ) - - self._new_window3 = ComponentViewer(ims, self.xdata, comp_spec, decon_spec, decomp_map) - self._new_window3.show() - - logger.info("Process complete") - - def kmeans_elbow(self): - logger.info("Process started..") - # self.update_stack() - - with pg.BusyCursor(): - try: - kmeans_variance(self.displayedStack) - logger.info("Process complete") - except OverflowError: - pass - logger.error("Overflow Error, values are too long") - - def kmeans_elbow_Thread(self): - # Pass the function to execute - worker = Worker(self.kmeans_elbow) # Any other args, kwargs are passed to the run function - worker.signals.result.connect(self.print_output) - worker.signals.finished.connect(self.thread_complete) - # Execute - self.threadpool.start(worker) - - def clustering_(self): - - logger.info("Process started..") - # self.update_stack() - method_ = self.cb_clust_method.currentText() - - decon_images, X_cluster, decon_spectra = cluster_stack( - self.displayedStack, - method=method_, - n_clusters_=self.sb_ncluster.value(), - decomposed=False, - decompose_method=self.cb_comp_method.currentText(), - decompose_comp=self.sb_ncomp.value(), - ) - - self._new_window4 = ClusterViewer(decon_images, self.xdata, X_cluster, decon_spectra) - self._new_window4.show() - - logger.info("Process complete") - - def change_color_on_load(self, button_name): - button_name.setStyleSheet("background-color : rgb(0,150,0);" "color: rgb(255,255,255)") - - def energyFileChooser(self): - file_name = QFileDialog().getOpenFileName(self, "Open energy list", "", "text file (*.txt)") - self.efilePath = file_name[0] - - def fast_xanes_fitting(self): - - self._new_window5 = XANESViewer(self.displayedStack, self.xdata, self.refs, self.ref_names) - self._new_window5.show() - - # Thread Signals - - def print_output(self, s): - print(s) - - def thread_complete(self): - print("THREAD COMPLETE!") - - def closeEvent(self, event): - reply = QMessageBox.question( - self, - "Window Close", - "Are you sure you want to close?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No, - ) - - if reply == QMessageBox.Yes: - event.accept() - QApplication.closeAllWindows() - else: - event.ignore() - - -class WorkerSignals(QObject): - """ - Defines the signals available from a running worker thread. - Supported signals are: - - finished: No data - - error:`tuple` (exctype, value, traceback.format_exc() ) - - result: `object` data returned from processing, anything - - progress: `tuple` indicating progress metadata - """ - - start = pyqtSignal() - finished = pyqtSignal() - error = pyqtSignal(tuple) - result = pyqtSignal(object) - - -class Worker(QRunnable): - """ - Worker thread - Inherits from QRunnable to handler worker thread setup, signals and wrap-up. - """ - - def __init__(self, fn, *args, **kwargs): - super(Worker, self).__init__() - # Store constructor arguments (re-used for processing) - self.fn = fn - self.args = args - self.kwargs = kwargs - self.signals = WorkerSignals() - - @pyqtSlot() - def run(self): - """ - Initialise the runner function with passed args, kwargs. - """ - # Retrieve args/kwargs here; and fire processing using them - self.signals.start.emit() - try: - result = self.fn(*self.args, **self.kwargs) - except Exception: - traceback.print_exc() - exctype, value = sys.exc_info()[:2] - self.signals.error.emit((exctype, value, traceback.format_exc())) - else: - self.signals.result.emit(result) # Return the result of the processing - finally: - self.signals.finished.emit() # Done - - -class singleStackViewer(QtWidgets.QMainWindow): - def __init__(self, img_stack, gradient="viridis"): - super(singleStackViewer, self).__init__() - - # Load the UI Page - uic.loadUi(os.path.join(ui_path, "uis/singleStackView.ui"), self) - - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - - self.img_stack = img_stack - self.gradient = gradient - self.image_view.setPredefinedGradient(gradient) - - if self.img_stack.ndim == 3: - self.dim1, self.dim3, self.dim2 = img_stack.shape - elif self.img_stack.ndim == 2: - self.dim3, self.dim2 = img_stack.shape - self.dim1 = 1 - self.hs_img_stack.setMaximum(self.dim1 - 1) - self.hs_img_stack.setValue(np.round(self.dim1 / 2)) - self.displayStack() - - # connections - self.hs_img_stack.valueChanged.connect(self.displayStack) - self.actionSave.triggered.connect(self.saveImageStackAsTIFF) - - def displayStack(self): - im_index = self.hs_img_stack.value() - if self.img_stack.ndim == 2: - self.image_view.setImage(self.img_stack) - else: - self.image_view.setImage(self.img_stack[im_index]) - self.label_img_count.setText(f"{im_index + 1}/{self.dim1}") - - def saveImageStackAsTIFF(self): - file_name = QFileDialog().getSaveFileName(self, "", "", "*.tiff;;*.tif") - if file_name[0]: - if self.img_stack.ndim == 3: - tf.imsave(str(file_name[0]), np.float32(self.img_stack.transpose(0, 2, 1))) - elif self.img_stack.ndim == 2: - tf.imsave(str(file_name[0]), np.float32(self.img_stack.T)) - else: - pass - - -class ComponentViewer(QtWidgets.QMainWindow): - def __init__(self, comp_stack, energy, comp_spectra, decon_spectra, decomp_map): - super(ComponentViewer, self).__init__() - - # Load the UI Page - uic.loadUi(os.path.join(ui_path, "uis/ComponentView.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - - self.comp_stack = comp_stack - self.energy = energy - self.comp_spectra = comp_spectra - self.decon_spectra = decon_spectra - self.decomp_map = decomp_map - - (self.dim1, self.dim3, self.dim2) = self.comp_stack.shape - self.hs_comp_number.setMaximum(self.dim1 - 1) - - self.image_view.setImage(self.comp_stack) - self.image_view.setPredefinedGradient("viridis") - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - - self.image_view2.setImage(self.decomp_map) - self.image_view2.setPredefinedGradient("bipolar") - self.image_view2.ui.menuBtn.hide() - self.image_view2.ui.roiBtn.hide() - - # connection - self.update_image() - self.pb_show_all.clicked.connect(self.show_all_spec) - self.hs_comp_number.valueChanged.connect(self.update_image) - self.actionSave.triggered.connect(self.save_comp_data) - self.pb_openScatterPlot.clicked.connect(self.openScatterPlot) - self.pb_showMultiColor.clicked.connect(self.generateMultiColorView) - - def update_image(self): - im_index = self.hs_comp_number.value() - self.spectrum_view.setLabel("bottom", "Energy") - self.spectrum_view.setLabel("left", "Intensity", "A.U.") - self.spectrum_view.plot(self.energy, self.decon_spectra[:, im_index], clear=True) - self.component_view.setLabel("bottom", "Energy") - self.component_view.setLabel("left", "Weight", "A.U.") - self.component_view.plot(self.energy, self.comp_spectra[:, im_index], clear=True) - self.label_comp_number.setText(f"{im_index + 1}/{self.dim1}") - # self.image_view.setCurrentIndex(im_index-1) - self.image_view.setImage(self.comp_stack[im_index]) - - def openScatterPlot(self): - self.scatter_window = ComponentScatterPlot(self.comp_stack, self.comp_spectra) - - # ph = self.geometry().height() - # pw = self.geometry().width() - # px = self.geometry().x() - # py = self.geometry().y() - # dw = self.scatter_window.width() - # dh = self.scatter_window.height() - # self.scatter_window.setGeometry(px+0.65*pw, py + ph - 2*dh-5, dw, dh) - self.scatter_window.show() - - def show_all_spec(self): - self.spectrum_view.clear() - self.plt_colors = ["g", "b", "r", "c", "m", "y", "w"] * 10 - offsets = np.arange(0, 2, 0.2) - self.spectrum_view.addLegend() - for ii in range(self.decon_spectra.shape[1]): - self.spectrum_view.plot( - self.energy, - (self.decon_spectra[:, ii] / self.decon_spectra[:, ii].max()) + offsets[ii], - pen=self.plt_colors[ii], - name="component" + str(ii + 1), - ) - - def save_comp_data(self): - file_name = QFileDialog().getSaveFileName(self, "", "", "data(*tiff *tif *txt *png )") - if file_name[0]: - tf.imsave( - str(file_name[0]) + "_components.tiff", np.float32(self.comp_stack.transpose(0, 2, 1)), imagej=True - ) - tf.imsave(str(file_name[0]) + "_component_masks.tiff", np.float32(self.decomp_map.T), imagej=True) - np.savetxt(str(file_name[0]) + "_deconv_spec.txt", self.decon_spectra) - np.savetxt(str(file_name[0]) + "_component_spec.txt", self.comp_spectra) - else: - pass - - def generateMultiColorView(self): - self.multichanneldict = {} - - for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.comp_stack.transpose(0, 1, 2))): - low, high = np.min(image), np.max(image) - self.multichanneldict[f"Image {n + 1}"] = { - "ImageName": f"Image {n + 1}", - "ImageDir": ".", - "Image": image, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) - self.muli_color_window.show() - - # add energy column - - -class ClusterViewer(QtWidgets.QMainWindow): - def __init__(self, decon_images, energy, X_cluster, decon_spectra): - super(ClusterViewer, self).__init__() - - # Load the UI Page - uic.loadUi(os.path.join(ui_path, "uis/ClusterView.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - - self.decon_images = decon_images - self.energy = energy - self.X_cluster = X_cluster - self.decon_spectra = decon_spectra - (self.dim1, self.dim3, self.dim2) = self.decon_images.shape - self.hsb_cluster_number.setMaximum(self.dim1 - 1) - self.X_cluster = X_cluster - - self.image_view.setImage(self.decon_images, autoHistogramRange=True, autoLevels=True) - self.image_view.setPredefinedGradient("viridis") - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - - self.cluster_view.setImage(self.X_cluster, autoHistogramRange=True, autoLevels=True) - self.cluster_view.setPredefinedGradient("bipolar") - self.cluster_view.ui.histogram.hide() - self.cluster_view.ui.menuBtn.hide() - self.cluster_view.ui.roiBtn.hide() - - # connection - self.update_display() - self.hsb_cluster_number.valueChanged.connect(self.update_display) - self.actionSave.triggered.connect(self.save_clust_data) - self.pb_show_all_spec.clicked.connect(self.showAllSpec) - self.pb_showMultiColor.clicked.connect(self.generateMultiColorView) - - def update_display(self): - im_index = self.hsb_cluster_number.value() - self.component_view.setLabel("bottom", "Energy") - self.component_view.setLabel("left", "Intensity", "A.U.") - self.component_view.plot(self.energy, self.decon_spectra[:, im_index], clear=True) - # self.image_view.setCurrentIndex(im_index-1) - self.image_view.setImage(self.decon_images[im_index]) - self.label_comp_number.setText(f"{im_index + 1}/{self.dim1}") - - def save_clust_data(self): - file_name = QFileDialog().getSaveFileName(self, "", "", "data(*tiff *tif *txt *png )") - if file_name[0]: - - tf.imsave( - str(file_name[0]) + "_cluster.tiff", np.float32(self.decon_images.transpose(0, 2, 1)), imagej=True - ) - tf.imsave(str(file_name[0]) + "_cluster_map.tiff", np.float32(self.X_cluster.T), imagej=True) - np.savetxt(str(file_name[0]) + "_deconv_spec.txt", self.decon_spectra) - - else: - logger.error("Saving Cancelled") - self.statusbar.showMessage("Saving Cancelled") - pass - - def showAllSpec(self): - self.component_view.clear() - self.plt_colors = ["g", "b", "r", "c", "m", "y", "w"] * 10 - offsets = np.arange(0, 2, 0.2) - self.component_view.addLegend() - for ii in range(self.decon_spectra.shape[1]): - self.component_view.plot( - self.energy, - (self.decon_spectra[:, ii] / self.decon_spectra[:, ii].max()) + offsets[ii], - pen=self.plt_colors[ii], - name="cluster" + str(ii + 1), - ) - - def generateMultiColorView(self): - self.multichanneldict = {} - - for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.decon_images.transpose(0, 1, 2))): - low, high = np.min(image), np.max(image) - self.multichanneldict[f"Image {n + 1}"] = { - "ImageName": f"Image {n + 1}", - "ImageDir": ".", - "Image": image, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) - self.muli_color_window.show() - - -class XANESViewer(QtWidgets.QMainWindow): - def __init__(self, im_stack=None, e_list=None, refs=None, ref_names=None): - super(XANESViewer, self).__init__() - - uic.loadUi(os.path.join(ui_path, "uis/XANESViewer.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - - self.im_stack = im_stack - self.e_list = e_list - self.refs = refs - self.ref_names = ref_names - self.selected = self.ref_names - self.fitResultDict = {} - self.fit_method = self.cb_xanes_fit_model.currentText() - self.alphaForLM = self.dsb_alphaForLM.value() - - self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( - self.im_stack, self.e_list, self.refs, method=self.fit_method, alphaForLM=self.alphaForLM - ) - - (self.dim1, self.dim2, self.dim3) = self.im_stack.shape - self.cn = int(self.dim2 // 2) - self.sz = np.max([int(self.dim2 * 0.15), int(self.dim3 * 0.15)]) - self.image_roi = pg.RectROI( - [int(self.dim3 // 2), int(self.dim2 // 2)], - [self.sz, self.sz], - pen="w", - maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), - ) - - self.image_roi.addTranslateHandle([0, 0], [2, 2]) - self.image_roi.addRotateHandle([0, 1], [2, 2]) - - # self.image_roi = pg.PolyLineROI([[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], - # pos=(int(self.dim2 // 2), int(self.dim3 // 2)), - # maxBounds=QtCore.QRect(0, 0, self.dim3, self.dim2), closed=True) - # self.image_roi.addTranslateHandle([self.sz // 2, self.sz // 2], [2, 2]) - - self.stack_center = int(self.dim1 // 2) - self.stack_width = int(self.dim1 * 0.05) - # self.image_view.setCurrentIndex(self.stack_center) - - self.image_view.addItem(self.image_roi) - self.xdata = self.e_list + self.sb_e_shift.value() - - self.scrollBar_setup() - self.display_image_data() - self.display_references() - self.update_spectrum() - - # connections - self.sb_e_shift.valueChanged.connect(self.update_spectrum) - self.pb_re_fit.clicked.connect(self.re_fit_xanes) - self.pb_edit_refs.clicked.connect(self.choose_refs) - self.image_roi.sigRegionChanged.connect(self.update_spectrum) - self.hsb_xanes_stk.valueChanged.connect(self.display_image_data) - self.hsb_chem_map.valueChanged.connect(self.display_image_data) - self.pb_showMultiColor.clicked.connect(self.generateMultiColorView) - self.pb_showCompSpec.clicked.connect(self.showComponentXANES) - - # menu - self.actionSave_Chem_Map.triggered.connect(self.save_chem_map) - self.actionSave_R_factor_Image.triggered.connect(self.save_rfactor_img) - self.actionSave_Live_Fit_Data.triggered.connect(self.pg_export_spec_fit) - self.actionExport_Fit_Stats.triggered.connect(self.exportFitResults) - self.actionExport_Ref_Plot.triggered.connect(self.pg_export_references) - - def scrollBar_setup(self): - self.hsb_xanes_stk.setValue(self.stack_center) - self.hsb_xanes_stk.setMaximum(self.dim1 - 1) - self.hsb_chem_map.setValue(0) - self.hsb_chem_map.setMaximum(self.decon_ims.shape[-1] - 1) - - def display_image_data(self): - - self.image_view.setImage(self.im_stack[self.hsb_xanes_stk.value()]) - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - self.image_view.setPredefinedGradient("viridis") - - self.image_view_maps.setImage(self.decon_ims.transpose(2, 0, 1)[self.hsb_chem_map.value()]) - self.image_view_maps.setPredefinedGradient("bipolar") - self.image_view_maps.ui.menuBtn.hide() - self.image_view_maps.ui.roiBtn.hide() - - def display_references(self): - - self.inter_ref = interploate_E(self.refs, self.xdata) - self.plt_colors = ["c", "m", "y", "w"] * 10 - self.spectrum_view_refs.addLegend() - for ii in range(self.inter_ref.shape[0]): - if len(self.selected) != 0: - self.spectrum_view_refs.plot( - self.xdata, - self.inter_ref[ii], - pen=pg.mkPen(self.plt_colors[ii], width=2), - name=self.selected[1:][ii], - ) - else: - self.spectrum_view_refs.plot( - self.xdata, - self.inter_ref[ii], - pen=pg.mkPen(self.plt_colors[ii], width=2), - name="ref" + str(ii + 1), - ) - - def choose_refs(self): - "Interactively exclude some standards from the reference file" - self.ref_edit_window = RefChooser( - self.ref_names, - self.im_stack, - self.e_list, - self.refs, - self.sb_e_shift.value(), - self.cb_xanes_fit_model.currentText(), - ) - self.ref_edit_window.show() - # self.rf_plot = pg.plot(title="RFactor Tracker") - - # connections - self.ref_edit_window.choosenRefsSignal.connect(self.update_refs) - self.ref_edit_window.fitResultsSignal.connect(self.plotFitResults) - - def update_refs(self, list_): - self.selected = list_ # list_ is the signal from ref chooser - self.update_spectrum() - self.re_fit_xanes() - - def update_spectrum(self): - - self.roi_img = self.image_roi.getArrayRegion(self.im_stack, self.image_view.imageItem, axes=(1, 2)) - sizex, sizey = self.roi_img.shape[1], self.roi_img.shape[2] - posx, posy = self.image_roi.pos() - self.roi_info.setText(f"ROI_Pos: {int(posx)},{int(posy)} ROI_Size: {sizex},{sizey}") - - self.xdata1 = self.e_list + self.sb_e_shift.value() - self.ydata1 = get_sum_spectra(self.roi_img) - self.fit_method = self.cb_xanes_fit_model.currentText() - - if len(self.selected) != 0: - - self.inter_ref = interploate_E(self.refs[self.selected], self.xdata1) - stats, coeffs = xanes_fitting_1D( - self.ydata1, - self.xdata1, - self.refs[self.selected], - method=self.fit_method, - alphaForLM=self.alphaForLM, - ) - - else: - self.inter_ref = interploate_E(self.refs, self.xdata1) - stats, coeffs = xanes_fitting_1D( - self.ydata1, self.xdata1, self.refs, method=self.fit_method, alphaForLM=self.alphaForLM - ) - - self.fit_ = np.dot(coeffs, self.inter_ref) - pen = pg.mkPen("g", width=1.5) - pen2 = pg.mkPen("r", width=1.5) - # pen3 = pg.mkPen("y", width=1.5) - self.spectrum_view.addLegend() - self.spectrum_view.setLabel("bottom", "Energy") - self.spectrum_view.setLabel("left", "Intensity", "A.U.") - self.spectrum_view.plot(self.xdata1, self.ydata1, pen=pen, name="Data", clear=True) - self.spectrum_view.plot(self.xdata1, self.fit_, name="Fit", pen=pen2) - - for n, (coff, ref, plt_clr) in enumerate(zip(coeffs, self.inter_ref, self.plt_colors)): - - if len(self.selected) != 0: - - self.spectrum_view.plot(self.xdata1, np.dot(coff, ref), name=self.selected[1:][n], pen=plt_clr) - else: - self.spectrum_view.plot(self.xdata1, np.dot(coff, ref), name="ref" + str(n + 1), pen=plt_clr) - # set the rfactor value to the line edit slot - self.results = ( - f"Coefficients: {coeffs} \n" - f"R-Factor: {stats['R_Factor']}, R-Square: {stats['R_Square']},\n " - f"Chi-Square: {stats['Chi_Square']}, " - f"Reduced Chi-Square: {stats['Reduced Chi_Square']}" - ) - - self.fit_results.setText(self.results) - - def re_fit_xanes(self): - if len(self.selected) != 0: - self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( - self.im_stack, - self.e_list + self.sb_e_shift.value(), - self.refs[self.selected], - method=self.cb_xanes_fit_model.currentText(), - alphaForLM=self.alphaForLM, - ) - else: - # if non athena file with no header is loaded no ref file cannot be edited - self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( - self.im_stack, - self.e_list + self.sb_e_shift.value(), - self.refs, - method=self.cb_xanes_fit_model.currentText(), - alphaForLM=self.alphaForLM, - ) - - # rfactor is a list of all spectra so take the mean - self.rfactor_mean = np.mean(self.rfactor) - self.image_view_maps.setImage(self.decon_ims.transpose(2, 0, 1)) - self.scrollBar_setup() - - def plotFitResults(self, decon_ims, rfactor_mean, coeff_array): - # upadte the chem maps and scrollbar params - self.image_view_maps.setImage(decon_ims.transpose(2, 0, 1)) - # self.hsb_chem_map.setValue(0) - # self.hsb_chem_map.setMaximum(decon_ims.shape[-1]-1) - - # set the rfactor value to the line edit slot - self.le_r_sq.setText(f"{rfactor_mean :.4f}") - - def showComponentXANES(self): - compNum = self.hsb_chem_map.value() - currentComp = self.decon_ims.transpose(2, 0, 1)[compNum] - currentCompMask = currentComp > 0 - yData = applyMaskGetMeanSpectrum(self.im_stack, currentCompMask) - xanes_comp_plot = pg.plot( - self.e_list + self.sb_e_shift.value(), - yData, - title=f"Component_{compNum}", - pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), - symbol="o", - ) - xanes_comp_plot.setLabel("bottom", "Energy (keV)") - xanes_comp_plot.setLabel("left", "Intensity") - - def generateMultiColorView(self): - self.multichanneldict = {} - - for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.decon_ims.transpose((2, 0, 1)))): - low, high = np.min(image), np.max(image) - self.multichanneldict[f"Image {n + 1}"] = { - "ImageName": f"Image {n + 1}", - "ImageDir": ".", - "Image": image, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) - self.muli_color_window.show() - - def save_chem_map(self): - file_name = QFileDialog().getSaveFileName(self, "save image", "chemical_map.tiff", "image data (*tiff)") - if file_name[0]: - tf.imsave(str(file_name[0]), np.float32(self.decon_ims.transpose(2, 0, 1)), imagej=True) - else: - logger.error("No file to save") - pass - - def save_rfactor_img(self): - file_name = QFileDialog().getSaveFileName(self, "save image", "r-factor_map.tiff", "image data (*tiff)") - if file_name[0]: - tf.imsave(str(file_name[0]), np.float32(self.rfactor), imagej=True) - else: - logger.error("No file to save") - pass - - def save_spec_fit(self): - try: - to_save = np.column_stack([self.xdata1, self.ydata1, self.fit_]) - file_name = QFileDialog().getSaveFileName(self, "save spectrum", "", "spectrum and fit (*txt)") - if file_name[0]: - np.savetxt(str(file_name[0]) + ".txt", to_save) - else: - pass - except Exception: - logger.error("No file to save") - pass - - def pg_export_spec_fit(self): - - exporter = pg.exporters.CSVExporter(self.spectrum_view.plotItem) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName(self, "save spectrum", "", "spectrum and fit (*csv)") - if file_name[0]: - exporter.export(str(file_name[0]) + ".csv") - else: - pass - - def pg_export_references(self): - - exporter = pg.exporters.CSVExporter(self.spectrum_view_refs.plotItem) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName( - self, "save references", "xanes_references.csv", "column data (*csv)" - ) - if file_name[0]: - exporter.export(str(file_name[0])) - else: - pass - - def exportFitResults(self): - file_name = QFileDialog().getSaveFileName(self, "save txt", "xanes_1D_fit_results.txt", "txt data (*txt)") - if file_name[0]: - with open(file_name[0], "w") as file: - file.write(self.results) - else: - pass - - -class RefChooser(QtWidgets.QMainWindow): - choosenRefsSignal: pyqtSignal = QtCore.pyqtSignal(list) - fitResultsSignal: pyqtSignal = QtCore.pyqtSignal(np.ndarray, float, np.ndarray) - - def __init__(self, ref_names, im_stack, e_list, refs, e_shift, fit_model): - super(RefChooser, self).__init__() - uic.loadUi(os.path.join(ui_path, "uis/RefChooser.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - self.ref_names = ref_names - self.refs = refs - self.im_stack = im_stack - self.e_list = e_list - self.e_shift = e_shift - self.fit_model = fit_model - - self.all_boxes = [] - self.rFactorList = [] - - self.displayCombinations() - - # selection become more apparent than default with red-ish color - self.tableWidget.setStyleSheet("background-color: white; selection-background-color: rgb(200,0,0);") - - # add a line to the plot to walk through the table. Note that the table is not sorted - self.selectionLine = pg.InfiniteLine( - pos=1, angle=90, pen=pg.mkPen("m", width=2.5), movable=True, bounds=None, label="Move Me!" - ) - self.stat_view.setLabel("bottom", "Fit ID") - self.stat_view.setLabel("left", "Reduced Chi^2") - - for n, i in enumerate(self.ref_names): - self.cb_i = QtWidgets.QCheckBox(self.ref_box_frame) - if n == 0: - self.cb_i.setChecked(True) - self.cb_i.setEnabled(False) - self.cb_i.setObjectName(i) - self.cb_i.setText(i) - self.gridLayout_2.addWidget(self.cb_i, n, 0, 1, 1) - self.cb_i.toggled.connect(self.enableApply) - self.all_boxes.append(self.cb_i) - - # connections - self.pb_apply.clicked.connect(self.clickedWhichAre) - self.pb_combo.clicked.connect(self.tryAllCombo) - self.actionExport_Results_csv.triggered.connect(self.exportFitResults) - self.selectionLine.sigPositionChanged.connect(self.updateFitWithLine) - self.tableWidget.itemSelectionChanged.connect(self.updateWithTableSelection) - # self.stat_view.scene().sigMouseClicked.connect(self.moveSelectionLine) - self.stat_view.mouseDoubleClickEvent = self.moveSelectionLine - self.sb_max_combo.valueChanged.connect(self.displayCombinations) - # self.pb_sort_with_r.clicked.connect(lambda: self.tableWidget.sortItems(3, QtCore.Qt.AscendingOrder)) - self.pb_sort_with_r.clicked.connect(self.sortTable) - self.cb_sorter.currentTextChanged.connect(self.sortTable) - - # def clickedWhich(self): - # button_name = self.sender() - - def populateChecked(self): - self.onlyCheckedBoxes = [] - for names in self.all_boxes: - if names.isChecked(): - self.onlyCheckedBoxes.append(names.objectName()) - - QtCore.pyqtSlot() - - def clickedWhichAre(self): - self.populateChecked() - self.choosenRefsSignal.emit(self.onlyCheckedBoxes) - - def generateRefList(self, ref_list, maxCombo, minCombo=1): - - """ - Creates a list of reference combinations for xanes fitting - - Paramaters; - - ref_list (list): list of ref names from the header - maxCombo (int): maximum number of ref lists in combination - minCombo (int): min number of ref lists in combination - - returns; - - 1. int: length of total number of combinations - 2. list: all the combinations - - """ - - if not maxCombo > len(ref_list): - - iter_list = [] - while minCombo < maxCombo + 1: - iter_list += list(combinations(ref_list, minCombo)) - minCombo += 1 - return len(iter_list), iter_list - - else: - raise ValueError(" Maximum numbinations cannot be larger than number of list items") - - def displayCombinations(self): - niter, self.iter_list = self.generateRefList(self.ref_names[1:], self.sb_max_combo.value()) - self.label_nComb.setText(str(niter) + " Combinations") - - @QtCore.pyqtSlot() - def tryAllCombo(self): - # empty list to to keep track and plot of reduced chi2 of all the fits - self.rfactor_list = [] - - # create dataframe for the table - self.df = pd.DataFrame( - columns=["Fit Number", "References", "Coefficients", "R-Factor", "R^2", "chi^2", "red-chi^2", "Score"] - ) - - # df columns is the header for the table widget - self.tableWidget.setHorizontalHeaderLabels(self.df.columns) - # self.iter_list = list(combinations(self.ref_names[1:],self.sb_max_combo.value())) - - niter, self.iter_list = self.generateRefList(self.ref_names[1:], self.sb_max_combo.value()) - tot_combo = len(self.iter_list) - for n, refs in enumerate(self.iter_list): - self.statusbar.showMessage(f"{n + 1}/{tot_combo}") - selectedRefs = list((str(self.ref_names[0]),) + refs) - self.fit_combo_progress.setValue((n + 1) * 100 / tot_combo) - self.stat, self.coeffs_arr = xanes_fitting_Binned( - self.im_stack, self.e_list + self.e_shift, self.refs[selectedRefs], method=self.fit_model - ) - - self.rfactor_list.append(self.stat["Reduced Chi_Square"]) - self.stat_view.plot( - x=np.arange(n + 1), - y=self.rfactor_list, - clear=True, - title="Reduced Chi^2", - pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), - symbol="o", - ) - - # arbitary number to rank the best fit - fit_score = (self.stat["R_Square"] + np.sum(self.coeffs_arr)) / ( - self.stat["R_Factor"] + self.stat["Reduced Chi_Square"] - ) - - resultsDict = { - "Fit Number": n, - "References": str(selectedRefs[1:]), - "Coefficients": str(np.around(self.coeffs_arr, 4)), - "Sum of Coefficients": str(np.around(np.sum(self.coeffs_arr), 4)), - "R-Factor": self.stat["R_Factor"], - "R^2": self.stat["R_Square"], - "chi^2": self.stat["Chi_Square"], - "red-chi^2": self.stat["Reduced Chi_Square"], - "Score": np.around(fit_score, 4), - } - - self.df = pd.concat([self.df, pd.DataFrame([resultsDict])], ignore_index=True) - - self.dataFrametoQTable(self.df) - QtTest.QTest.qWait(0.1) # hepls with real time plotting - - self.stat_view.addItem(self.selectionLine) - - def dataFrametoQTable(self, df_: pd.DataFrame): - nRows = len(df_.index) - nColumns = len(df_.columns) - self.tableWidget.setRowCount(nRows) - self.tableWidget.setColumnCount(nColumns) - self.tableWidget.setHorizontalHeaderLabels(df_.columns) - - for i in range(nRows): - for j in range(nColumns): - cell = QtWidgets.QTableWidgetItem(str(df_.values[i][j])) - self.tableWidget.setItem(i, j, cell) - - # set the property of the table view. Size policy to make the contents justified - self.tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - self.tableWidget.resizeColumnsToContents() - - def exportFitResults(self): - file_name = QFileDialog().getSaveFileName(self, "save csv", "xanes_fit_results_log.csv", "txt data (*csv)") - if file_name[0]: - with open(str(file_name[0]), "w") as fp: - self.df.to_csv(fp) - else: - pass - - def selectTableAndCheckBox(self, x): - nSelection = int(round(x)) - self.tableWidget.selectRow(nSelection) - fit_num = int(self.tableWidget.item(nSelection, 0).text()) - refs_selected = self.iter_list[fit_num] - - # reset all the checkboxes to uncheck state, except the energy - for checkstate in self.findChildren(QtWidgets.QCheckBox): - if checkstate.isEnabled(): - checkstate.setChecked(False) - - for cb_names in refs_selected: - checkbox = self.findChild(QtWidgets.QCheckBox, name=cb_names) - checkbox.setChecked(True) - - def updateFitWithLine(self): - pos_x, pos_y = self.selectionLine.pos() - x = self.df.index[self.df[str("Fit Number")] == np.round(pos_x)][0] - self.selectTableAndCheckBox(x) - - def updateWithTableSelection(self): - x = self.tableWidget.currentRow() - self.selectTableAndCheckBox(x) - - def moveSelectionLine(self, event): - if event.button() == QtCore.Qt.LeftButton: - Pos = self.stat_view.plotItem.vb.mapSceneToView(event.pos()) - self.selectionLine.setPos(Pos.x()) - - def sortTable(self): - sorter_dict = { - "R-Factor": "R-Factor", - "R-Square": "R^2", - "Chi-Square": "chi^2", - "Reduced Chi-Square": "red-chi^2", - "Fit Number": "Fit Number", - } - sorter = sorter_dict[self.cb_sorter.currentText()] - self.df = self.df.sort_values(sorter, ignore_index=True) - self.dataFrametoQTable(self.df) - - def enableApply(self): - - """ """ - self.populateChecked() - if len(self.onlyCheckedBoxes) > 1: - self.pb_apply.setEnabled(True) - else: - self.pb_apply.setEnabled(False) - - -class ScatterPlot(QtWidgets.QMainWindow): - def __init__(self, img1, img2, nameTuple): - super(ScatterPlot, self).__init__() - - uic.loadUi(os.path.join(ui_path, "uis/ScatterView.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - self.clearPgPlot() - self.w1 = self.scatterViewer.addPlot() - self.img1 = img1 - self.img2 = img2 - self.nameTuple = nameTuple - x, y = np.shape(self.img1) - self.s1 = pg.ScatterPlotItem(size=2, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 0, 255)) - # print(self.s1) - - # create three polyline ROIs for masking - Xsize = self.img1.max() / 6 - Ysize = self.img2.max() / 6 - - self.scatter_mask = pg.PolyLineROI( - [[0, 0], [0, Ysize], [Xsize / 2, Ysize * 1.5], [Xsize, Ysize], [Xsize, 0]], - pos=None, - pen=pg.mkPen("r", width=2), - hoverPen=pg.mkPen("w", width=2), - closed=True, - removable=True, - ) - - self.scatter_mask2 = pg.PolyLineROI( - [ - [Xsize * 1.2, 0], - [Xsize * 1.2, Ysize * 2], - [Xsize * 2, Ysize * 2], - [Xsize * 3, Ysize], - [Xsize * 2, 0], - ], - pos=None, - pen=pg.mkPen("g", width=2), - hoverPen=pg.mkPen("w", width=2), - closed=True, - removable=True, - ) - self.scatter_mask3 = pg.PolyLineROI( - [ - [Xsize * 2.5, 0], - [Xsize * 2.5, Ysize], - [Xsize * 4, Ysize], - [Xsize * 4, 0], - [Xsize * 3.7, Ysize * -0.5], - ], - pos=None, - pen=pg.mkPen("c", width=2), - hoverPen=pg.mkPen("w", width=2), - closed=True, - removable=True, - ) - - self.fitScatter = self.fitScatter2 = self.fitScatter3 = None - - self.rois = { - "ROI 1": (self.scatter_mask, self.rb_roi1.isChecked(), self.fitScatter), - "ROI 2": (self.scatter_mask2, self.rb_roi2.isChecked(), self.fitScatter2), - "ROI 3": (self.scatter_mask3, self.rb_roi3.isChecked(), self.fitScatter3), - } - - self.windowNames = {"ROI 1": self.fitScatter, "ROI 2": self.fitScatter2, "ROI 3": self.fitScatter3} - - self.s1.setData(self.img1.flatten(), self.img2.flatten()) - self.w1.setLabel("bottom", self.nameTuple[0], "counts") - self.label_img1.setText(self.nameTuple[0]) - self.w1.setLabel("left", self.nameTuple[1], "counts") - self.label_img2.setText(self.nameTuple[1]) - self.w1.addItem(self.s1) - - self.image_view.setImage(self.img1) - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - self.image_view.setPredefinedGradient("thermal") - - self.image_view2.setImage(self.img2) - self.image_view2.ui.menuBtn.hide() - self.image_view2.ui.roiBtn.hide() - self.image_view2.setPredefinedGradient("thermal") - - # connections - self.actionSave_Plot.triggered.connect(self.pg_export_correlation) - self.actionSave_Images.triggered.connect(self.tiff_export_images) - # self.pb_define_mask.clicked.connect(lambda:self.createMask(self.scatter_mask)) - self.pb_define_mask.clicked.connect(self.addMultipleROIs) - # self.pb_apply_mask.clicked.connect(lambda:self.getMaskRegion(self.scatter_mask)) - self.pb_apply_mask.clicked.connect(self.applyMultipleROIs) - self.pb_clear_mask.clicked.connect(self.clearMultipleROIs) - self.pb_compositeScatter.clicked.connect(self.createCompositeScatter) - [rbs.clicked.connect(self.updateROIDict) for rbs in [self.rb_roi1, self.rb_roi2, self.rb_roi3]] - - def pg_export_correlation(self): - - exporter = pg.exporters.CSVExporter(self.w1) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName(self, "save correlation", "", "spectrum and fit (*csv)") - if file_name[0]: - exporter.export(str(file_name[0]) + ".csv") - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - pass - - def tiff_export_images(self): - file_name = QFileDialog().getSaveFileName(self, "save images", "", "spectrum and fit (*tiff)") - if file_name[0]: - tf.imsave(str(file_name[0]) + ".tiff", np.dstack([self.img1, self.img2]).T) - self.statusbar.showMessage(f"Images saved to {str(file_name[0])}") - else: - pass - - def createMask(self, ROIName): - - try: - self.w1.removeItem(ROIName) - except Exception: - pass - self.w1.addItem(ROIName) - - def clearMask(self, ROIName): - self.w1.removeItem(ROIName) - - def clearPgPlot(self): - try: - self.masked_img.close() - except Exception: - pass - - def getMaskRegion(self, ROIName, generateSeperateWindows=True): - - """filter scatterplot points using polylineROI region""" - - # Ref : https://stackoverflow.com/questions/57719303/how-to-map-mouse-position-on-a-scatterplot - - # get the roi region:QPaintPathObject - roiShape = self.rois[ROIName][0].mapToItem(self.s1, self.rois[ROIName][0].shape()) - - # get data in the scatter plot - scatterData = np.array(self.s1.getData()) - - # generate a binary mask for points inside or outside the roishape - selected = [roiShape.contains(QtCore.QPointF(pt[0], pt[1])) for pt in scatterData.T] - - # reshape the mask to image dimensions - self.mask2D = np.reshape(selected, (self.img1.shape)) - - # get masked image1 - self.maskedImage = self.mask2D * self.img1 - - # get rid of the (0,0) values in the masked array - self.xData, self.yData = np.compress(selected, scatterData[0]), np.compress(selected, scatterData[1]) - - # linear regeression of the filtered X,Y data - result = linregress(self.xData, self.yData) - - # Pearson's correlation of the filtered X,Y data - pr, pp = stats.pearsonr(self.xData, self.yData) - - # apply the solved equation to xData to generate the fit line - self.yyData = result.intercept + result.slope * self.xData - - # Prepare strings for fit results and stats - self.fitLineEqn = ( - f" y = x*{result.slope :.3e} + {result.intercept :.3e}, " - "R^2 = {result.rvalue**2 :.3f}, r = {pr :.3f}\n" - ) - FitStats1 = f" Slope Error = {result.stderr :.3e}, Intercept Error = {result.intercept_stderr :.3e}\n" - FitStats2 = f" Pearson’s correlation coefficient = {pr :.3f}" - refs = "\n\n ***References****\n\n scipy.stats.linregress, scipy.stats.pearsonr " - fitStats = ( - f"\n ***{ROIName} Fit Results***\n\n" + " Equation: " + self.fitLineEqn + FitStats1 + FitStats2 + refs - ) - - # generate new window to plot the results - - if generateSeperateWindows: - self.windowNames[ROIName] = MaskedScatterPlotFit( - [self.xData, self.yData], - [self.xData, self.yyData], - self.mask2D, - self.maskedImage, - fitStats, - self.fitLineEqn, - self.nameTuple, - ) - self.windowNames[ROIName].show() - - """ - from scipy.linalg import lstsq - M = xData[:, np.newaxis]**[0, 1] #use >1 for polynomial fits - p, res, rnk, s = lstsq(M, yData) - yyData = p[0] + p[1]*xData - """ - - def updateROIDict(self): - self.rois = { - "ROI 1": (self.scatter_mask, self.rb_roi1.isChecked()), - "ROI 2": (self.scatter_mask2, self.rb_roi2.isChecked()), - "ROI 3": (self.scatter_mask3, self.rb_roi3.isChecked()), - } - - def applyMultipleROIs(self): - with pg.BusyCursor(): - self.updateROIDict() - for key in self.rois.keys(): - if self.rois[key][1]: - self.getMaskRegion(key) - else: - pass - - def addMultipleROIs(self): - self.updateROIDict() - for key in self.rois.keys(): - if self.rois[key][1]: - self.createMask(self.rois[key][0]) - else: - self.clearMask(self.rois[key][0]) - - def clearMultipleROIs(self): - self.updateROIDict() - for key in self.rois.keys(): - if not self.rois[key][1]: - self.clearMask(self.rois[key][0]) - else: - pass - - def createCompositeScatter(self): - - points = [] - fitLine = [] - masks = [] - roiFitEqn = {} - - self.updateROIDict() - for n, key in enumerate(self.rois.keys()): - if self.rois[key][1]: - self.getMaskRegion(key, generateSeperateWindows=False) - points.append(np.column_stack([self.xData, self.yData])) - fitLine.append(np.column_stack([self.xData, self.yyData])) - masks.append(self.mask2D) - roiFitEqn[key] = self.fitLineEqn - else: - pass - - logger.info(f" fitline shape: {np.shape(fitLine)}") - logger.info(f" points shape: {np.shape(points)}") - logger.info(f" maks shape: {np.shape(masks)}") - self.compositeScatterWindow = CompositeScatterPlot( - np.array(points), np.array(fitLine), np.array(masks), roiFitEqn, self.nameTuple - ) - self.compositeScatterWindow.show() - - def _createCompositeScatter(self): - self.scatterColors = ["w", "c", "y", "k", "m"] - points = [] - fitLine = [] - - self.updateROIDict() - for n, key in enumerate(self.rois.keys()): - if self.rois[key][1]: - self.getMaskRegion(key, generateSeperateWindows=False) - - for x, y, yy in zip(self.xData, self.yData, self.yyData): - - points.append( - { - "pos": (x, y), - "data": "id", - "size": 3, - "pen": pg.mkPen(None), - "brush": self.scatterColors[n], - } - ) - fitLine.extend(np.column_stack((self.xData, self.yyData))) - else: - pass - - logger.info(f" fitline shape: {np.shape(fitLine)}") - self.compositeScatterWindow = CompositeScatterPlot(points, np.array(fitLine)) - self.compositeScatterWindow.show() - - def getROIParams(self): - print(np.array(self.scatter_mask.getSceneHandlePositions())) - - -class MaskedScatterPlotFit(QtWidgets.QMainWindow): - def __init__(self, scatterData, fitData, mask, maskedImage, fitString, fitEquation, nameTuple): - super(MaskedScatterPlotFit, self).__init__() - - uic.loadUi(os.path.join(ui_path, "uis/maskedScatterPlotFit.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - self.scatterData = scatterData - self.fitData = fitData - self.mask = mask - self.maskedImage = maskedImage - self.fitString = fitString - self.fitEquation = fitEquation - self.nameTuple = nameTuple - - # set the graphicslayoutwidget in the ui as canvas - self.canvas = self.scatterViewer.addPlot() - self.canvas.addLegend() - self.canvas.setLabel("bottom", self.nameTuple[0], "counts") - self.canvas.setLabel("left", self.nameTuple[1], "counts") - self.gb_maskedImage1.setTitle(f" Masked {self.nameTuple[0]}") - - # generate a scatter plot item - self.scattered = pg.ScatterPlotItem(size=3.5, pen=pg.mkPen(None), brush=pg.mkBrush(5, 214, 255, 200)) - - # set scatter plot data - self.scattered.setData(scatterData[0], scatterData[1], name="Data") - - # set z value negative to show scatter data behind the fit line - self.scattered.setZValue(-10) - - # add scatter plot to the canvas - self.canvas.addItem(self.scattered) - - # generate plotitem for fit line - self.fitLinePlot = pg.PlotDataItem(pen=pg.mkPen(pg.mkColor(220, 20, 60), width=3.3)) - - # set line plot data - self.fitLinePlot.setData(fitData[0], fitData[1], name="Linear Fit") - - # add line plot to the canvas - self.canvas.addItem(self.fitLinePlot) - - # display Mask - self.imageView_mask.setImage(self.mask) - self.imageView_mask.ui.menuBtn.hide() - self.imageView_mask.ui.roiBtn.hide() - self.imageView_mask.setPredefinedGradient("plasma") - - # display masked Image - self.imageView_maskedImage.setImage(self.maskedImage) - self.imageView_maskedImage.ui.menuBtn.hide() - self.imageView_maskedImage.ui.roiBtn.hide() - self.imageView_maskedImage.setPredefinedGradient("viridis") - - # display Fit stats - self.text_fit_results.setPlainText(fitString) - self.canvas.setTitle(self.fitEquation, color="r") - - # connections - self.pb_copy_results.clicked.connect(self.copyFitResults) - self.pb_save_results.clicked.connect(self.saveFitResults) - self.actionSave_Plot.triggered.connect(self.pg_export_correlation) - self.actionSaveMask.triggered.connect(self.saveMask) - self.actionSaveMaskedImage.triggered.connect(self.saveImage) - - def saveFitResults(self): - S__File = QFileDialog.getSaveFileName(self, "save txt", "correlationPlotFit.txt", "txt data (*txt)") - - Text = self.text_fit_results.toPlainText() - if S__File[0]: - with open(S__File[0], "w") as file: - file.write(Text) - - def copyFitResults(self): - self.text_fit_results.selectAll() - self.text_fit_results.copy() - self.statusbar.showMessage("text copied to clipboard") - - def pg_export_correlation(self): - - exporter = pg.exporters.CSVExporter(self.canvas) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName( - self, "save correlation", "scatterData.csv", "spectrum and fit (*csv)" - ) - if file_name[0]: - exporter.export(str(file_name[0])) - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - pass - - def saveImage(self): - - file_name = QFileDialog().getSaveFileName(self, "Save image data", "image.tiff", "image file(*tiff *tif )") - if file_name[0]: - tf.imsave(str(file_name[0]), self.maskedImage) - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - self.statusbar.showMessage("Saving cancelled") - pass - - def saveMask(self): - - file_name = QFileDialog().getSaveFileName(self, "Save image data", "mask.tiff", "image file(*tiff *tif )") - if file_name[0]: - tf.imsave(str(file_name[0]), self.mask) - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - self.statusbar.showMessage("Saving cancelled") - pass - - -class ComponentScatterPlot(QtWidgets.QMainWindow): - def __init__(self, decomp_stack, specs): - super(ComponentScatterPlot, self).__init__() - - uic.loadUi(os.path.join(ui_path, "uis/ComponentScatterPlot.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - self.w1 = self.scatterViewer.addPlot() - self.decomp_stack = decomp_stack - self.specs = specs - (self.dim1, self.dim3, self.dim2) = self.decomp_stack.shape - # fill the combonbox depending in the number of components for scatter plot - for n, combs in enumerate(combinations(np.arange(self.dim1), 2)): - self.cb_scatter_comp.addItem(str(combs)) - self.cb_scatter_comp.setItemData(n, combs) - - self.s1 = pg.ScatterPlotItem(size=3, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 0, 120)) - - self.setImageAndScatterPlot() - # connections - self.actionSave_Plot.triggered.connect(self.pg_export_correlation) - self.actionSave_Images.triggered.connect(self.tiff_export_images) - self.pb_updateComponents.clicked.connect(self.setImageAndScatterPlot) - self.pb_define_mask.clicked.connect(self.createMask) - self.pb_apply_mask.clicked.connect(self.getMaskRegion) - self.pb_reset_mask.clicked.connect(self.resetMask) - self.pb_addALine.clicked.connect(lambda: self.createMask(Line=True)) - - def setImageAndScatterPlot(self): - - try: - self.s1.clear() - except Exception: - pass - - comp_tuple = self.cb_scatter_comp.currentData() - self.img1, self.img2 = self.decomp_stack[comp_tuple[0]], self.decomp_stack[comp_tuple[-1]] - self.image_view.setImage(self.decomp_stack[comp_tuple[0]]) - self.image_view.ui.menuBtn.hide() - self.image_view.ui.roiBtn.hide() - self.image_view.setPredefinedGradient("bipolar") - - self.image_view2.setImage(self.decomp_stack[comp_tuple[-1]]) - self.image_view2.ui.menuBtn.hide() - self.image_view2.ui.roiBtn.hide() - self.image_view2.setPredefinedGradient("bipolar") - - points = [] - for i, j in zip(self.img1.flatten(), self.img2.flatten()): - - points.append( - { - "pos": (i, j), - "data": "id", - "size": 5, - "pen": pg.mkPen(None), - "brush": pg.mkBrush(255, 255, 0, 160), - } - ) - - self.s1.addPoints(points) - self.w1.addItem(self.s1) - # self.s1.setData(self.specs[:, comp_tuple[0]], self.specs[:, comp_tuple[-1]]) - self.w1.setLabel("bottom", f"PC{comp_tuple[0]+1}") - self.w1.setLabel("left", f"PC{comp_tuple[-1]+1}") - self.label_im1.setText(f"PC{comp_tuple[0]+1}") - self.label_im2.setText(f"PC{comp_tuple[-1]+1}") - - def createMask(self, Line=False): - - self.size = self.img1.max() / 10 - self.pos = int(self.img1.mean()) - - if Line: - self.lineROI = pg.LineSegmentROI( - [0, 1], - pos=(self.pos, self.pos), - pen=pg.mkPen("r", width=4), - hoverPen=pg.mkPen("g", width=4), - removable=True, - ) - self.w1.addItem(self.lineROI) - - else: - - self.scatter_mask = pg.PolyLineROI( - [[0, 0], [0, self.size], [self.size, self.size], [self.size, 0]], - pos=(self.pos, self.pos), - pen=pg.mkPen("r", width=4), - hoverPen=pg.mkPen("g", width=4), - closed=True, - removable=True, - ) - - self.w1.addItem(self.scatter_mask) - - def resetMask(self): - self.clearMask() - self.createMask() - - def clearMask(self): - try: - self.w1.removeItem(self.scatter_mask) - except AttributeError: - pass - - def clearPgPlot(self): - try: - self.masked_img.close() - except Exception: - pass - - def getMaskRegion(self): - - # Ref : https://stackoverflow.com/questions/57719303/how-to-map-mouse-position-on-a-scatterplot - - roiShape = self.scatter_mask.mapToItem(self.s1, self.scatter_mask.shape()) - self._points = list() - logger.info("Building Scatter Plot Window; Please wait..") - for i in range(len(self.img1.flatten())): - self._points.append(QtCore.QPointF(self.img1.flatten()[i], self.img2.flatten()[i])) - - selected = [roiShape.contains(pt) for pt in self._points] - img_selected = np.reshape(selected, (self.img1.shape)) - - self.masked_img = singleStackViewer(img_selected * self.img1, gradient="bipolar") - self.masked_img.show() - - def pg_export_correlation(self): - - exporter = pg.exporters.CSVExporter(self.w1) - exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" - file_name = QFileDialog().getSaveFileName(self, "save correlation", "", "spectrum and fit (*csv)") - if file_name[0]: - exporter.export(str(file_name[0]) + ".csv") - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - pass - - def tiff_export_images(self): - file_name = QFileDialog().getSaveFileName(self, "save images", "", "spectrum and fit (*tiff)") - if file_name[0]: - tf.imsave(str(file_name[0]) + ".tiff", np.dstack([self.img1, self.img2]).T) - self.statusbar.showMessage(f"Images saved to {str(file_name[0])}") - else: - pass - - -class LoadingScreen(QtWidgets.QSplashScreen): - def __init__(self): - super(LoadingScreen, self).__init__() - uic.loadUi(os.path.join(ui_path, "uis/animationWindow.ui"), self) - self.setWindowOpacity(0.65) - self.movie = QMovie("uis/animation.gif") - self.label.setMovie(self.movie) - - def mousePressEvent(self, event): - # disable default "click-to-dismiss" behaviour - pass - - def startAnimation(self): - self.movie.start() - self.show() - - def stopAnimation(self): - self.movie.stop() - self.hide() - - -class CompositeScatterPlot(QtWidgets.QMainWindow): - def __init__(self, scatterPoints, fitLine, maskImages, fitEquations, nameTuple): - super(CompositeScatterPlot, self).__init__() - - uic.loadUi(os.path.join(ui_path, "uis/multipleScatterFit.ui"), self) - self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) - - self.scatterPoints = scatterPoints - self.fitLine = fitLine - self.scatterColors = ["r", (0, 115, 0), (4, 186, 186), "c", "w", "k"] - self.fitColors = ["b", "r", "m", "k", "b"] - self.roiNames = list(fitEquations.keys()) - self.fitEqns = list(fitEquations.values()) - self.nameTuple = nameTuple - self.maskImages = maskImages - - # self.scatterViewer.setBackground('w') - # set the graphicslayoutwidget in the ui as canvas - self.canvas = self.scatterViewer.addPlot() - self.canvas.addLegend() - self.canvas.setLabel("bottom", self.nameTuple[0], "counts") - self.canvas.setLabel("left", self.nameTuple[1], "counts") - - # connections - self.actionExport.triggered.connect(self.exportData) - self.actionSave_as_PNG.triggered.connect(self.exportAsPNG) - self.actionGenerate_MultiColor_Mask.triggered.connect(self.generateMultiColorView) - self.actionWhite.triggered.connect(lambda: self.scatterViewer.setBackground("w")) - self.actionBlack.triggered.connect(lambda: self.scatterViewer.setBackground("k")) - - with pg.BusyCursor(): - - for arr, fitline, clr, fitClr, rname, feqn in zip( - self.scatterPoints, self.fitLine, self.scatterColors, self.fitColors, self.roiNames, self.fitEqns - ): - - sctrPoints = [] - for pt in arr: - sctrPoints.append( - {"pos": (pt[0], pt[1]), "data": "id", "size": 3, "pen": pg.mkPen(None), "brush": clr} - ) - - # generate a scatter plot item - self.scattered = pg.ScatterPlotItem(size=4.5, pen=clr, brush=pg.mkBrush(5, 214, 255, 200)) - # set scatter plot data - self.scattered.setPoints(sctrPoints, name=rname) - - # set z value negative to show scatter data behind the fit line - self.scattered.setZValue(-10) - - # add scatter plot to the canvas - self.canvas.addItem(self.scattered) - - # generate plotitem for fit line - self.fitLinePlot = pg.PlotDataItem(pen=pg.mkPen(fitClr, width=4.5)) - - # set line plot data - self.fitLinePlot.setData(fitline, name=feqn) - - # add line plot to the canvas - self.canvas.addItem(self.fitLinePlot) - - def generateMultiColorView(self): - self.multichanneldict = {} - - for n, (colorName, image, rname) in enumerate(zip(cmap_dict.keys(), self.maskImages, self.roiNames)): - low, high = np.min(image), np.max(image) - self.multichanneldict[rname] = { - "ImageName": rname, - "ImageDir": ".", - "Image": image, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - - # print( self.multichanneldict) - self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) - self.muli_color_window.show() - - def exportData(self): - - exporter = pg.exporters.CSVExporter(self.canvas) - # exporter.parameters()['columnMode'] = '(x,y,y,y) for all plots' - file_name = QFileDialog().getSaveFileName(self, "Save CSV Data", "scatter.csv", "image file (*csv)") - if file_name[0]: - exporter.export(str(file_name[0])) - self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") - else: - pass - - def exportAsPNG(self): - file_name = QtWidgets.QFileDialog().getSaveFileName( - self, "Save Image", "image.png", "PNG(*.png);; TIFF(*.tiff);; JPG(*.jpg)" - ) - exporter = pg.exporters.ImageExporter(self.canvas) - - if file_name[0]: - exporter.export(str(file_name[0])) - self.statusbar.showMessage(f"Image saved to {str(file_name[0])}") - else: - pass - - -class MaskSpecViewer(QtWidgets.QMainWindow): - def __init__(self, xanes_stack=None, xrf_map=None, energy=[]): - super(MaskSpecViewer, self).__init__() - uic.loadUi(os.path.join(ui_path, "uis/MaskedView.ui"), self) - - self.xanes_stack = xanes_stack - self.xrf_map = xrf_map - self.energy = energy - self.xrf_map = self.xanes_stack[-1] - self.view_data() - - # connections - self.sldr_xrf_low.valueChanged.connect(self.create_mask) - self.sldr_xrf_high.valueChanged.connect(self.create_mask) - self.pb_apply_mask.clicked.connect(self.apply_mask_to_xanes) - self.pb_export_mask.clicked.connect(self.export_mask) - self.pb_import_mask.clicked.connect(self.import_a_mask) - self.actionLoad_Energy_List.triggered.connect(self.load_energy) - self.actionLoad_XANES_Stack.triggered.connect(self.load_xanes_stack) - self.actionLoad_XRF_Map.triggered.connect(self.load_xrf_map) - - def view_data(self): - - self.xanes_view.setImage(self.xanes_stack) - self.xanes_view.ui.menuBtn.hide() - self.xanes_view.ui.roiBtn.hide() - (self.dim1, self.dim3, self.dim2) = self.xanes_stack.shape - self.xanes_view.setPredefinedGradient("viridis") - self.xanes_view.setCurrentIndex(self.dim1 // 2) - self.statusbar.showMessage("One image from the XANES stack is used as mask") - self.xrf_view.setImage(self.xrf_map) - self.xrf_view.ui.menuBtn.hide() - self.xrf_view.ui.roiBtn.hide() - self.xrf_view.setPredefinedGradient("viridis") - - self.mask_view.ui.menuBtn.hide() - self.mask_view.ui.roiBtn.hide() - - def create_mask(self): - self.threshold_low = np.around(self.sldr_xrf_low.value() * 0.01, 3) - self.threshold_high = np.around(self.sldr_xrf_high.value() * 0.01, 3) - self.sldr_xrf_low.setMaximum(self.sldr_xrf_high.value() + 1) - self.sldr_xrf_high.setMinimum(self.sldr_xrf_low.value() + 1) - self.norm_xrf_map = remove_nan_inf(self.xrf_map) / remove_nan_inf(self.xrf_map.max()) - self.norm_xrf_map[self.norm_xrf_map < self.threshold_low] = 0 - self.norm_xrf_map[self.norm_xrf_map > self.threshold_high] = 0 - self.xrf_view.setImage(self.norm_xrf_map) - self.le_sldr_vals.setText(str(self.threshold_low) + " to " + str(self.threshold_high)) - self.statusbar.showMessage("New Threshold Applied") - self.xrf_mask = np.where(self.norm_xrf_map > 0, self.norm_xrf_map, 0) - self.xrf_mask[self.xrf_mask > 0] = 1 - self.mask_view.setImage(self.xrf_mask) - - def load_xanes_stack(self): - """loading a new xanes stack""" - filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") - self.file_name = str(filename[0]) - self.xanes_stack = tf.imread(self.file_name).transpose(0, 2, 1) - self.view_data() - - def load_energy(self): - """To load energy list that will be used for plotting the spectra. - number of stack should match length of energy list""" - - file_name = QFileDialog().getOpenFileName(self, "Open energy list", "", "text file (*.txt)") - - try: - self.energy = np.loadtxt(str(file_name[0])) - logger.info("Energy file loaded") - assert len(self.energy) == self.dim1 - self.view_data() - - except OSError: - logger.error("No File selected") - pass - - def load_xrf_map(self): - """To xrf map for masking. If 3D mean will be taken""" - - filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") - self.xrf_file_name = str(filename[0]) - self.xrf_map = tf.imread(self.xrf_file_name) - if self.xrf_map.ndim == 3: - self.xrf_map = self.xrf_map.mean(0).T - - else: - self.xrf_map = self.xrf_map.T - - assert ( - self.dim3, - self.dim2, - ) == self.xrf_map.shape, f"Unexpected image dimensions: {self.xrf_map.shape} vs {(self.dim2,self.dim3)}" - - self.view_data() - self.create_mask() - - def apply_mask_to_xanes(self): - - """Generates a mask with 0 and 1 from the choosen threshold and multply with the xanes stack. - A spectrum will be generated from the new masked stack""" - - self.masked_xanes = self.xanes_stack * self.xrf_mask - self.xanes_view.setImage(self.masked_xanes) - self.xanes_view.setCurrentIndex(self.dim1 // 2) - self.statusbar.showMessage("Mask Applied to XANES") - self.mask_spec = get_mean_spectra(self.masked_xanes) - - if len(self.energy) != 0: - self.xdata = self.energy - else: - self.xdata = np.arange(0, self.dim1) - self.statusbar.showMessage("No Energy List Available; Integer values are used for plotting") - - self.spectrum_view.plot(self.xdata, self.mask_spec, clear=True) - - def import_a_mask(self): - filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") - xrf_file_name = str(filename[0]) - self.xrf_mask = tf.imread(xrf_file_name).T - self.statusbar.showMessage("A New Mask Imported") - self.mask_view.setImage(self.xrf_mask) - self.apply_mask_to_xanes() - - def export_mask(self): - try: - file_name = QFileDialog().getSaveFileName(self, "Save image data", "", "image file(*tiff *tif )") - tf.imsave(str(file_name[0]) + ".tiff", self.xrf_mask.T) - logger.info(f"Updated Image Saved: {str(file_name[0])}") - self.statusbar.showMessage("Mask Exported") - except Exception: - logger.error("No file to save") - pass - - -class StackInfo(QtWidgets.QMainWindow): - def __init__(self, text_to_write: str = " "): - super(StackInfo, self).__init__() - uic.loadUi(os.path.join(ui_path, "uis/log.ui"), self) - - self.text_to_write = text_to_write - self.pte_run_cmd.setPlainText(self.text_to_write) - - # connections - self.pb_save_cmd.clicked.connect(self.save_file) - self.pb_clear_cmd.clicked.connect(self.clear_text) - - def save_file(self): - S__File = QFileDialog.getSaveFileName(None, "SaveFile", "/", "txt Files (*.txt)") - - Text = self.pte_run_cmd.toPlainText() - if S__File[0]: - with open(S__File[0], "w") as file: - file.write(Text) - - def clear_text(self): - self.pte_run_cmd.clear() - - -class MultiChannelWindow(QtWidgets.QMainWindow): - def __init__(self, image_dict=None): - super(MultiChannelWindow, self).__init__() - if image_dict is None: - image_dict = {} - uic.loadUi(os.path.join(ui_path, "uis/mutlichannel.ui"), self) - - self.canvas = self.img_view.addPlot(title="") - self.canvas.getViewBox().invertY(True) - self.canvas.setAspectLocked(True) - self.cb_choose_color.addItems([i for i in cmap_dict.keys()]) - - self.image_dict = image_dict - self.buildFromDictionary() - - # connections - self.actionLoad.triggered.connect(self.createMuliColorAndList) - self.actionLoad_Stack.triggered.connect(self.createMuliColorAndList) - self.cb_choose_color.currentTextChanged.connect(self.updateImageDictionary) - self.pb_update_low_high.clicked.connect(self.updateImageDictionary) - self.listWidget.itemClicked.connect(self.editImageProperties) - self.listWidget.itemDoubleClicked.connect(self.showOneImageOnly) - self.pb_show_selected.clicked.connect(self.showOneImageOnly) - self.pb_show_all.clicked.connect(self.showAllItems) - self.actionLoad_State_File.triggered.connect(self.importState) - self.actionSave_State.triggered.connect(self.exportState) - self.actionSave_View.triggered.connect(self.saveImage) - - def buildFromDictionary(self): - if self.image_dict is not None: - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - else: - pass - - def generateImageDictionary(self): - """Creates a dictionary contains image path, color scheme chosen, throshold limits etc. - when user edits the parameters dictionary will be updated and unwrapped for display later. - This dictionary is saved as json file while saving the state. Two image loading options are possible. - User can either select multiple 2D array images or one 3D array (stack)""" - - clickedAction = self.sender() - - if clickedAction.text() == "Load Images": - # multiple images are selected - self.loadMultipleImageFiles() - - elif clickedAction.text() == "Load Stack": - # an image stack is selected - self.loadAsStack() - - def loadMultipleImageFiles(self): - - filter = "TIFF (*.tiff);;TIF (*.tif)" - QtWidgets.QFileDialog().setFileMode(QtWidgets.QFileDialog.ExistingFiles) - # choose mutliple tiff files - names = QtWidgets.QFileDialog().getOpenFileNames(self, "Open files", " ", filter) - if names[0]: - self.image_dict = {} - # select the file directory. Image files are expected to be in the same folder - self.imageDir = os.path.dirname(names[0][0]) - - # create the dictionary - for colorName, image in zip(cmap_dict.keys(), names[0]): - # squeeze to allow with pseudo 3D axis from some tomo recon (eg. 1, 100,100 array) - im_array = np.squeeze(tf.imread(image)) - # set values for thresholding as image min and max - low, high = np.min(im_array), np.max(im_array) - # name of the tiff file is chosen as key for the dictionary, - # inner keys are properties set for that image - im_name = os.path.basename(image) - # construct the dictionary - self.image_dict[f"{os.path.basename(image)}"] = { - "ImageName": im_name, - "ImageDir": self.imageDir, - "Image": im_array, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - else: - pass - - def loadAsStack(self): - """construct the dictionary with image +number as the key. - All other steps are similar to the loadMultipleImageFiles function""" - - filter = "TIFF (*.tiff);;TIF (*.tif)" - file_name = QtWidgets.QFileDialog().getOpenFileName( - self, "Open a Stack", "", "TIFF(*tiff *tif);;all_files (*)", filter - ) - if file_name[0]: - self.imageDir = os.path.dirname(file_name[0]) - self.image_dict = {} - im_stack = np.squeeze(tf.imread(file_name[0])) - # asset the file is a stack - assert im_stack.ndim == 3, "Not a stack" - # construct the dictionary - for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), im_stack)): - low, high = np.min(image), np.max(image) - self.image_dict[f"Image {n+1}"] = { - "ImageName": f"Image {n+1}", - "ImageDir": self.imageDir, - "Image": image, - "Color": colorName, - "CmapLimits": (low, high), - "Opacity": 1.0, - } - - def loadAnImage(self, image, colormap, cmap_limits, opacity=1): - """load single image and colorbar to the widget. This function will be looped for - multiple images later - """ - # get pg image item - img = pg.ImageItem() - # add image to the graphicsview widget - self.canvas.addItem(img) - # set the color map - cmap = pg.ColorMap(pos=np.linspace(0, 1, len(colormap)), color=colormap) - # image = np.squeeze(tf.imread(image_path)) - # set image to the image item with cmap - img.setImage(np.array(image), lut=cmap.getLookupTable(), opacity=opacity) - - # set colorbar for thresholding - bar = pg.ColorBarItem(values=cmap_limits, cmap=cmap, limits=(0, None), orientation="vertical") - bar.setImageItem(img) - # set composition mode to plus for overlaying - img.setCompositionMode(QtGui.QPainter.CompositionMode_Plus) - - def createMultiColorView(self, image_dictionary): - """Function creates multi color image view by taking image - data and parameters from the dictionary""" - - # clear the plots and list in case of re-loading - self.canvas.clear() - self.listWidget.clear() - - # display individual images in for loop - for path_and_color in image_dictionary.values(): - self.loadAnImage( - path_and_color["Image"], - cmap_dict[path_and_color["Color"]], - path_and_color["CmapLimits"], - path_and_color["Opacity"], - ) - - def showOneImageOnly(self): - editItem = self.listWidget.currentItem() - editRow = self.listWidget.currentRow() - for i in range(self.listWidget.count()): - if self.listWidget.item(i) == editItem: - editItemName = self.listWidget.item(i).text().split(",")[0] - self.image_dict[editItemName]["Opacity"] = 1 - - elif self.listWidget.item(i) != editItem: - editItemName = self.listWidget.item(i).text().split(",")[0] - self.image_dict[editItemName]["Opacity"] = 0 - - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - self.listWidget.setCurrentRow(editRow) - - def showAllItems(self): - # editItem = self.listWidget.currentItem() - editRow = self.listWidget.currentRow() - for i in range(self.listWidget.count()): - editItemName = self.listWidget.item(i).text().split(",")[0] - self.image_dict[editItemName]["Opacity"] = 1 - - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - self.listWidget.setCurrentRow(editRow) - - def displayImageNames(self, image_dictionary): - """Populate the list widget table with image name and color used to plot, - using image dictionary input""" - - for im_name, vals in image_dictionary.items(): - self.listWidget.addItem(f"{im_name},{vals['Color']}") - self.listWidget.setCurrentRow(0) - - def createMuliColorAndList(self): - """Finally Load Images and poplulate the list widget from the dictionary""" - with pg.BusyCursor(): # gives the circle showing gui is doing something - self.generateImageDictionary() - if self.image_dict: - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - - else: - pass - - def sliderSetUp(self, im_array): - """Setting the slider min and max from image values""" - - low = (np.min(im_array) / np.max(im_array)) * 100 - self.sldr_low.setMaximum(100) - self.sldr_low.setMinimum(low) - self.sldr_high.setMaximum(100) - self.sldr_high.setMinimum(low) - - def editImageProperties(self, item): - """function to control the assigned properties such as color, - threshold limits, opacity etc of a single image selected using the list widget item""" - - editItem = item.text() - # get the dictionary key from item text - editItemName = editItem.split(",")[0] - editItemColor = editItem.split(",")[1] - im_array = self.image_dict[editItemName]["Image"] - self.sliderSetUp(im_array) - setValLow = (self.image_dict[editItemName]["CmapLimits"][0] * 100) / np.max(im_array) - setValHigh = (self.image_dict[editItemName]["CmapLimits"][1] * 100) / np.max(im_array) - setOpacity = self.image_dict[editItemName]["Opacity"] * 100 - self.sldr_low.setValue(int(setValLow)) - self.sldr_high.setValue(int(setValHigh)) - self.sldr_opacity.setValue(int(setOpacity)) - self.low_high_vals.setText(f"low:{self.sldr_low.value()}," f"high:{self.sldr_high.value()}") - self.cb_choose_color.setCurrentText(editItemColor) - - def updateImageDictionary(self): - newColor = self.cb_choose_color.currentText() - editItem = self.listWidget.currentItem().text() - editRow = self.listWidget.currentRow() - editItemName = editItem.split(",")[0] - self.imageDir = self.image_dict[editItemName]["ImageDir"] - im_array = self.image_dict[editItemName]["Image"] - self.sliderSetUp(im_array) - cmap_limits = ( - self.sldr_low.value() * np.max(im_array) / 100, - self.sldr_high.value() * np.max(im_array) / 100, - ) - self.low_high_vals.setText(f"low:{cmap_limits[0]:.3f},high:{cmap_limits[1]:.3f}") - opacity = self.sldr_opacity.value() / 100 - self.opacity_val.setText(str(opacity)) - self.image_dict[editItemName] = { - "ImageName": editItemName, - "ImageDir": self.imageDir, - "Image": im_array, - "Color": newColor, - "CmapLimits": cmap_limits, - "Opacity": opacity, - } - - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - self.listWidget.setCurrentRow(editRow) - - def exportState(self): - - file_name = QtWidgets.QFileDialog().getSaveFileName( - self, "Save Current State", "multicolor_params.json", "json file(*json)" - ) - """ - for val in self.image_dict.values(): - val['CmapLimits'] = json.dumps(str(val['CmapLimits'])) - """ - - if file_name[0]: - - with open(f"{file_name[0]}", "w") as fp: - json.dump(self.image_dict, fp, indent=4, cls=jsonEncoder) - - else: - pass - - def importState(self): - file_name = QtWidgets.QFileDialog().getOpenFileName( - self, "Open a State File", "", "json file(*json);;all_files (*)" - ) - if file_name[0]: - with open(file_name[0], "r") as fp: - self.image_dict = json.load(fp) - - self.createMultiColorView(self.image_dict) - self.displayImageNames(self.image_dict) - else: - pass - - def saveImage(self): - file_name = QtWidgets.QFileDialog().getSaveFileName( - self, "Save Image", "multicolor_image.png", "PNG(*.png);; TIFF(*.tiff);; JPG(*.jpg)" - ) - exporter = pg.exporters.ImageExporter(self.canvas.getViewBox()) - exporter.export(file_name[0]) - - -""" Helper Functions""" - - -def get_xrf_data(h="h5file"): - """ - get xrf stack from h5 data generated at NSLS-II beamlines - - Arguments: - h5/hdf5 file - - Returns: - norm_xrf_stack - xrf stack image normalized with Io - mono_e - excitation enegy used for xrf - beamline - identity of the beamline - Io_avg - an average Io value, used before taking log - - """ - - f = h5py.File(h, "r") - - if list(f.keys())[0] == "xrfmap": - logger.info("Data from HXN/TES/SRX") - beamline = f["xrfmap/scan_metadata"].attrs["scan_instrument_id"] - - try: - - beamline_scalar = {"HXN": 2, "SRX": 0, "TES": 0} - - if beamline in beamline_scalar.keys(): - - Io = np.array(f["xrfmap/scalers/val"])[:, :, beamline_scalar[beamline]] - raw_xrf_stack = np.array(f["xrfmap/detsum/counts"]) - norm_xrf_stack = raw_xrf_stack - Io_avg = int(remove_nan_inf(Io).mean()) - else: - logger.error("Unknown Beamline Scalar") - except Exception: - logger.warning("Unknown Scalar: Raw Detector count in use") - norm_xrf_stack = np.array(f["xrfmap/detsum/counts"]) - - elif list(f.keys())[0] == "xrmmap": - logger.info("Data from XFM") - beamline = "XFM" - raw_xrf_stack = np.array(f["xrmmap/mcasum/counts"]) - Io = np.array(f["xrmmap/scalars/I0"]) - norm_xrf_stack = raw_xrf_stack - Io_avg = int(remove_nan_inf(Io).mean()) - - else: - logger.error("Unknown Data Format") - - try: - mono_e = int(f["xrfmap/scan_metadata"].attrs["instrument_mono_incident_energy"] * 1000) - logger.info("Excitation energy was taken from the h5 data") - - except Exception: - mono_e = 12000 - logger.info(f"Unable to get Excitation energy from the h5 data; using default value {mono_e} KeV") - - return remove_nan_inf(norm_xrf_stack.transpose((2, 0, 1))), mono_e + 1500, beamline, Io_avg - - -def remove_nan_inf(im): - im = np.array(im, dtype=np.float32) - im[np.isnan(im)] = 0 - im[np.isinf(im)] = 0 - return im - - -def rebin_image(im, bin_factor): - arrx, arry = np.shape(im) - if arrx / bin_factor != int or arrx / bin_factor != int: - logger.error("Invalid Binning") - - else: - shape = (arrx / bin_factor, arry / bin_factor) - return im.reshape(shape).mean(-1).mean(1) - - -def remove_hot_pixels(image_array, NSigma=5): - image_array = remove_nan_inf(image_array) - a, b, c = np.shape(image_array) - img_stack2 = np.zeros((a, b, c)) - for i in range(a): - im = image_array[i, :, :] - im[abs(im) > np.std(im) * NSigma] = im.mean() - img_stack2[i, :, :] = im - return img_stack2 - - -def smoothen(image_array, w_size=5): - a, b, c = np.shape(image_array) - spec2D_Matrix = np.reshape(image_array, (a, (b * c))) - smooth2D_Matrix = savgol_filter(spec2D_Matrix, w_size, w_size - 2, axis=0) - return remove_nan_inf(np.reshape(smooth2D_Matrix, (a, b, c))) - - -def resize_stack(image_array, upscaling=False, scaling_factor=2): - en, im1, im2 = np.shape(image_array) - - if upscaling: - im1_ = im1 * scaling_factor - im2_ = im2 * scaling_factor - img_stack_resized = resize(image_array, (en, im1_, im2_)) - - else: - im1_ = int(im1 / scaling_factor) - im2_ = int(im2 / scaling_factor) - img_stack_resized = resize(image_array, (en, im1_, im2_)) - - return img_stack_resized - - -def normalize(image_array, norm_point=-1): - norm_stack = image_array / image_array[norm_point] - return remove_nan_inf(norm_stack) - - -def remove_edges(image_array): - # z, x, y = np.shape(image_array) - return image_array[:, 1:-1, 1:-1] - - -def background_value(image_array): - img = image_array.mean(0) - img_h = img.mean(0) - img_v = img.mean(1) - h = np.gradient(img_h) - v = np.gradient(img_v) - bg = np.min([img_h[h == h.max()], img_v[v == v.max()]]) - return bg - - -def background_subtraction(img_stack, bg_percentage=10): - img_stack = remove_nan_inf(img_stack) - a, b, c = np.shape(img_stack) - ref_image = np.reshape(img_stack.mean(0), (b * c)) - bg_ratio = int((b * c) * 0.01 * bg_percentage) - bg_ = np.max(sorted(ref_image)[0:bg_ratio]) - bged_img_stack = img_stack - bg_[:, np.newaxis, np.newaxis] - return bged_img_stack - - -def background_subtraction2(img_stack, bg_percentage=10): - img_stack = remove_nan_inf(img_stack) - a, b, c = np.shape(img_stack) - bg_ratio = int((b * c) * 0.01 * bg_percentage) - bged_img_stack = img_stack.copy() - - for n, img in enumerate(img_stack): - bg_ = np.max(sorted(img.flatten())[0:bg_ratio]) - print(bg_) - bged_img_stack[n] = img - bg_ - - return remove_nan_inf(bged_img_stack) - - -def background1(img_stack): - img = img_stack.sum(0) - img_h = img.mean(0) - img_v = img.mean(1) - h = np.gradient(img_h) - v = np.gradient(img_v) - bg = np.min([img_h[h == h.max()], img_v[v == v.max()]]) - return bg - - -def get_sum_spectra(image_array): - spec = np.sum(image_array, axis=(1, 2)) - return spec - - -def get_mean_spectra(image_array): - spec = np.mean(image_array, axis=(1, 2)) - return spec - - -def flatten_(image_array): - z, x, y = np.shape(image_array) - flat_array = np.reshape(image_array, (x * y, z)) - return flat_array - - -def image_to_pandas(image_array): - a, b, c = np.shape(image_array) - im_array = np.reshape(image_array, ((b * c), a)) - a, b = im_array.shape - df = pd.DataFrame( - data=im_array[:, :], columns=["e" + str(i) for i in range(b)], index=["s" + str(i) for i in range(a)] - ) - return df - - -def image_to_pandas2(image_array): - a, b, c = np.shape(image_array) - im_array = np.reshape(image_array, (a, (b * c))) - a, b = im_array.shape - df = pd.DataFrame( - data=im_array[:, :], index=["e" + str(i) for i in range(a)], columns=["s" + str(i) for i in range(b)] - ) - return df - - -def neg_log(image_array): - absorb = -1 * np.log(image_array) - return remove_nan_inf(absorb) - - -def clean_stack(img_stack, auto_bg=False, bg_percentage=5): - a, b, c = np.shape(img_stack) - - if auto_bg is True: - bg_ = background1(img_stack) - - else: - sum_spec = (img_stack.sum(1)).sum(1) - ref_stk_num = np.where(sum_spec == sum_spec.max())[-1] - - ref_image = np.reshape(img_stack[ref_stk_num], (b * c)) - bg_ratio = int((b * c) * 0.01 * bg_percentage) - bg_ = np.max(sorted(ref_image)[0:bg_ratio]) - - bg = np.where(img_stack[ref_stk_num] > bg_, img_stack[ref_stk_num], 0) - bg2 = np.where(bg < bg_, bg, 1) - - bged_img_stack = img_stack * bg2 - - return remove_nan_inf(bged_img_stack) - - -def subtractBackground(im_stack, bg_region): - if bg_region.ndim == 3: - bg_region_ = np.mean(bg_region, axis=(1, 2)) - - elif bg_region.ndim == 2: - bg_region_ = np.mean(bg_region, axis=1) - - else: - bg_region_ = bg_region - - return im_stack - bg_region_[:, np.newaxis, np.newaxis] - - -def classify(img_stack, correlation="Pearson"): - img_stack_ = img_stack - a, b, c = np.shape(img_stack_) - norm_img_stack = normalize(img_stack_) - f = np.reshape(norm_img_stack, (a, (b * c))) - - max_x, max_y = np.where(norm_img_stack.sum(0) == (norm_img_stack.sum(0)).max()) - ref = norm_img_stack[:, int(max_x), int(max_y)] - corr = np.zeros(len(f.T)) - for s in range(len(f.T)): - if correlation == "Kendall": - r, p = stats.kendalltau(ref, f.T[s]) - elif correlation == "Pearson": - r, p = stats.pearsonr(ref, f.T[s]) - - corr[s] = r - - cluster_image = np.reshape(corr, (b, c)) - return (cluster_image**3), img_stack_ - - -def correlation_kmeans(img_stack, n_clusters, correlation="Pearson"): - img, bg_image = classify(img_stack, correlation) - img[np.isnan(img)] = -99999 - X = img.reshape((-1, 1)) - k_means = sc.KMeans(n_clusters) - k_means.fit(X) - - X_cluster = k_means.labels_ - X_cluster = X_cluster.reshape(img.shape) + 1 - - return X_cluster - - -def cluster_stack( - im_array, method="KMeans", n_clusters_=4, decomposed=False, decompose_method="PCA", decompose_comp=2 -): - a, b, c = im_array.shape - - if method == "Correlation-Kmeans": - - X_cluster = correlation_kmeans(im_array, n_clusters_, correlation="Pearson") - - else: - - methods = { - "MiniBatchKMeans": sc.MiniBatchKMeans, - "KMeans": sc.KMeans, - "MeanShift": sc.MeanShift, - "Spectral Clustering": sc.SpectralClustering, - "Affinity Propagation": sc.AffinityPropagation, - } - - if decomposed: - im_array = denoise_with_decomposition(im_array, method_=decompose_method, n_components=decompose_comp) - - flat_array = np.reshape(im_array, (a, (b * c))) - init_cluster = methods[method](n_clusters=n_clusters_) - init_cluster.fit(np.transpose(flat_array)) - X_cluster = init_cluster.labels_.reshape(b, c) + 1 - - decon_spectra = np.zeros((a, n_clusters_)) - decon_images = np.zeros((n_clusters_, b, c)) - - for i in range(n_clusters_): - mask_i = np.where(X_cluster == (i + 1), X_cluster, 0) - spec_i = get_sum_spectra(im_array * mask_i) - decon_spectra[:, i] = spec_i - decon_images[i] = im_array.sum(0) * mask_i - - return decon_images, X_cluster, decon_spectra - - -def kmeans_variance(im_array): - a, b, c = im_array.shape - flat_array = np.reshape(im_array, (a, (b * c))) - var = np.arange(24) - clust_n = np.arange(24) + 2 - - for clust in var: - init_cluster = sc.KMeans(n_clusters=int(clust + 2)) - init_cluster.fit(np.transpose(flat_array)) - var_ = init_cluster.inertia_ - var[clust] = np.float64(var_) - - kmeans_var_plot = pg.plot( - clust_n, var, title="KMeans Variance", pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), symbol="o" - ) - kmeans_var_plot.setLabel("bottom", "Cluster Number") - kmeans_var_plot.setLabel("left", "Sum of squared distances") - - -def pca_scree(im_stack): - new_image = im_stack.transpose(2, 1, 0) - x, y, z = np.shape(new_image) - img_ = np.reshape(new_image, (x * y, z)) - pca = sd.PCA(z) - pca.fit(img_) - # var = pca.explained_variance_ratio_ - var = pca.singular_values_ - - pca_scree_plot = pg.plot( - var[:24], title="PCA Scree Plot", pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), symbol="o" - ) - pca_scree_plot.addLine(y=0) - pca_scree_plot.setLabel("bottom", "Component Number") - pca_scree_plot.setLabel("left", "Singular Values") - - -def decompose_stack(im_stack, decompose_method="PCA", n_components_=3): - new_image = im_stack.transpose(2, 1, 0) - x, y, z = np.shape(new_image) - img_ = np.reshape(new_image, (x * y, z)) - methods_dict = { - "PCA": sd.PCA, - "IncrementalPCA": sd.IncrementalPCA, - "NMF": sd.NMF, - "FastICA": sd.FastICA, - "DictionaryLearning": sd.MiniBatchDictionaryLearning, - "FactorAnalysis": sd.FactorAnalysis, - "TruncatedSVD": sd.TruncatedSVD, - } - - _mdl = methods_dict[decompose_method](n_components=n_components_) - - ims = (_mdl.fit_transform(img_).reshape(x, y, n_components_)).transpose(2, 1, 0) - spcs = _mdl.components_.transpose() - decon_spetra = np.zeros((z, n_components_)) - decom_map = np.zeros((ims.shape)) - - for i in range(n_components_): - f = ims.copy()[i] - f[f < 0] = 0 - spec_i = ((new_image.T * f).sum(1)).sum(1) - decon_spetra[:, i] = spec_i - - f[f > 0] = i + 1 - decom_map[i] = f - decom_map = decom_map.sum(0) - - return np.float32(ims), spcs, decon_spetra, decom_map - - -def denoise_with_decomposition(img_stack, method_="PCA", n_components=4): - new_image = img_stack.transpose(2, 1, 0) - x, y, z = np.shape(new_image) - img_ = np.reshape(new_image, (x * y, z)) - - methods_dict = { - "PCA": sd.PCA, - "IncrementalPCA": sd.IncrementalPCA, - "NMF": sd.NMF, - "FastICA": sd.FastICA, - "DictionaryLearning": sd.DictionaryLearning, - "FactorAnalysis": sd.FactorAnalysis, - "TruncatedSVD": sd.TruncatedSVD, - } - - decomposed = methods_dict[method_](n_components=n_components) - - ims = (decomposed.fit_transform(img_).reshape(x, y, n_components)).transpose(2, 1, 0) - ims[ims < 0] = 0 - ims[ims > 0] = 1 - mask = ims.sum(0) - mask[mask > 1] = 1 - # mask = uniform_filter(mask) - filtered = img_stack * mask - # plt.figure() - # plt.imshow(filtered.sum(0)) - # plt.title('background removed') - # plt.show() - return remove_nan_inf(filtered) - - -def interploate_E(refs, e): - n = np.shape(refs)[1] - refs = np.array(refs) - ref_e = refs[:, 0] - ref = refs[:, 1:n] - all_ref = [] - for i in range(n - 1): - ref_i = np.interp(e, ref_e, ref[:, i]) - all_ref.append(ref_i) - return np.array(all_ref) - - -def getStats(spec, fit, num_refs=2): - stats = {} - - r_factor = (np.sum(spec - fit) ** 2) / np.sum(spec**2) - stats["R_Factor"] = np.around(r_factor, 5) - - y_mean = np.sum(spec) / len(spec) - SS_tot = np.sum((spec - y_mean) ** 2) - SS_res = np.sum((spec - fit) ** 2) - r_square = 1 - (SS_res / SS_tot) - stats["R_Square"] = np.around(r_square, 4) - - chisq = np.sum((spec - fit) ** 2) - stats["Chi_Square"] = np.around(chisq, 5) - - red_chisq = chisq / (len(spec) - num_refs) - stats["Reduced Chi_Square"] = red_chisq - - return stats - - -def xanes_fitting_1D(spec, e_list, refs, method="NNLS", alphaForLM=0.01): - """Linear combination fit of image data with reference standards""" - - int_refs = interploate_E(refs, e_list) - - if method == "NNLS": - coeffs, r = opt.nnls(int_refs.T, spec) - - elif method == "LASSO": - lasso = linear_model.Lasso(positive=True, alpha=alphaForLM) # lowering alpha helps with 1D fits - fit_results = lasso.fit(int_refs.T, spec) - coeffs = fit_results.coef_ - - elif method == "RIDGE": - ridge = linear_model.Ridge(alpha=alphaForLM) - fit_results = ridge.fit(int_refs.T, spec) - coeffs = fit_results.coef_ - - fit = coeffs @ int_refs - stats = getStats(spec, fit, num_refs=np.min(np.shape(int_refs.T))) - - return stats, coeffs - - -def xanes_fitting(im_stack, e_list, refs, method="NNLS", alphaForLM=0.1, binStack=False): - """Linear combination fit of image data with reference standards""" - - if binStack: - im_stack = resize_stack(im_stack, scaling_factor=4) - - en, im1, im2 = np.shape(im_stack) - im_array = im_stack.reshape(en, im1 * im2) - coeffs_arr = [] - r_factor_arr = [] - # lasso = linear_model.Lasso(positive=True, alpha=alphaForLM) - for n, i in enumerate(range(im1 * im2)): - stats, coeffs = xanes_fitting_1D(im_array[:, i], e_list, refs, method=method, alphaForLM=alphaForLM) - coeffs_arr.append(coeffs) - r_factor_arr.append(stats["R_Factor"]) - - abundance_map = np.reshape(coeffs_arr, (im1, im2, -1)) - r_factor_im = np.reshape(r_factor_arr, (im1, im2)) - - return abundance_map, r_factor_im, np.mean(coeffs_arr, axis=0) - - -def xanes_fitting_Line(im_stack, e_list, refs, method="NNLS", alphaForLM=0.05): - """Linear combination fit of image data with reference standards""" - en, im1, im2 = np.shape(im_stack) - im_array = np.mean(im_stack, 2) - coeffs_arr = [] - meanStats = {"R_Factor": 0, "R_Square": 0, "Chi_Square": 0, "Reduced Chi_Square": 0} - - for i in range(im1): - stats, coeffs = xanes_fitting_1D(im_array[:, i], e_list, refs, method=method, alphaForLM=alphaForLM) - coeffs_arr.append(coeffs) - for key in stats.keys(): - meanStats[key] += stats[key] - - for key, vals in meanStats.items(): - meanStats[key] = np.around((vals / im1), 5) - - return meanStats, np.mean(coeffs_arr, axis=0) - - -def xanes_fitting_Binned(im_stack, e_list, refs, method="NNLS", alphaForLM=0.05): - """Linear combination fit of image data with reference standards""" - - im_stack = resize_stack(im_stack, scaling_factor=10) - # use a simple filter to find threshold value - val = filters.threshold_otsu(im_stack[-1]) - en, im1, im2 = np.shape(im_stack) - im_array = im_stack.reshape(en, im1 * im2) - coeffs_arr = [] - meanStats = {"R_Factor": 0, "R_Square": 0, "Chi_Square": 0, "Reduced Chi_Square": 0} - - specs_fitted = 0 - total_spec = im1 * im2 - for i in range(total_spec): - spec = im_array[:, i] - # do not fit low intensity/background regions - if spec[-1] > val: - specs_fitted += 1 - stats, coeffs = xanes_fitting_1D(spec / spec[-1], e_list, refs, method=method, alphaForLM=alphaForLM) - coeffs_arr.append(coeffs) - for key in stats.keys(): - meanStats[key] += stats[key] - else: - pass - - for key, vals in meanStats.items(): - meanStats[key] = np.around((vals / specs_fitted), 6) - # print(f"{specs_fitted}/{total_spec}") - return meanStats, np.mean(coeffs_arr, axis=0) - - -def create_df_from_nor(athenafile="fe_refs.nor"): - """create pandas dataframe from athena nor file, first column - is energy and headers are sample names""" - - refs = np.loadtxt(athenafile) - n_refs = refs.shape[-1] - skip_raw_n = n_refs + 6 - - df = pd.read_table( - athenafile, delim_whitespace=True, skiprows=skip_raw_n, header=None, usecols=np.arange(0, n_refs) - ) - df2 = pd.read_table( - athenafile, delim_whitespace=True, skiprows=skip_raw_n - 1, usecols=np.arange(0, n_refs + 1) - ) - new_col = df2.columns.drop("#") - df.columns = new_col - return df, list(new_col) - - -def create_df_from_nor_try2(athenafile="fe_refs.nor"): - """create pandas dataframe from athena nor file, first column - is energy and headers are sample names""" - - refs = np.loadtxt(athenafile) - n_refs = refs.shape[-1] - df_refs = pd.DataFrame(refs) - - df = pd.read_csv(athenafile, header=None) - new_col = list((str(df.iloc[n_refs + 5].values)).split(" ")[2::2]) - df_refs.columns = new_col - - return df_refs, list(new_col) - - -def energy_from_logfile(logfile="maps_log_tiff.txt"): - df = pd.read_csv(logfile, header=None, delim_whitespace=True, skiprows=9) - return df[9][df[7] == "energy"].values.astype(float) - - -def xanesNormalization( - e, mu, e0=7125, step=None, nnorm=2, nvict=0, pre1=None, pre2=-50, norm1=100, norm2=None, guess=False -): - if guess: - result = preedge(e, mu, e0, step=step, nnorm=nnorm, nvict=nvict) - - return result["pre1"], result["pre2"], result["norm1"], result["norm2"] - - else: - result = preedge(e, mu, e0, step, nnorm, nvict, pre1, pre2, norm1, norm2) - - return result["pre_edge"], result["post_edge"], result["norm"] - - -def xanesNormStack( - e_list, im_stack, e0=7125, step=None, nnorm=2, nvict=0, pre1=None, pre2=-50, norm1=100, norm2=None -): - - en, im1, im2 = np.shape(im_stack) - im_array = im_stack.reshape(en, im1 * im2) - normedStackArray = np.zeros_like(im_array) - - for i in range(im1 * im2): - pre_line, post_line, normXANES = xanesNormalization( - e_list, - im_array[:, i], - e0=e0, - step=step, - nnorm=nnorm, - nvict=nvict, - pre1=pre1, - pre2=pre2, - norm1=norm1, - norm2=norm2, - guess=False, - ) - normedStackArray[:, i] = normXANES - - return remove_nan_inf(np.reshape(normedStackArray, (en, im1, im2))) - - -def align_stack( - stack_img, ref_image_void=True, ref_stack=None, transformation=StackReg.TRANSLATION, reference="previous" -): - - """Image registration flow using pystack reg""" - - # all the options are in one function - - sr = StackReg(transformation) - - if ref_image_void: - tmats_ = sr.register_stack(stack_img, reference=reference) - - else: - tmats_ = sr.register_stack(ref_stack, reference=reference) - # out_ref = sr.transform_stack(ref_stack) - - out_stk = sr.transform_stack(stack_img, tmats=tmats_) - return np.float32(out_stk), tmats_ - - -def align_simple(stack_img, transformation=StackReg.TRANSLATION, reference="previous"): - - sr = StackReg(transformation) - tmats_ = sr.register_stack(stack_img, reference="previous") - for i in range(10): - out_stk = sr.transform_stack(stack_img, tmats=tmats_) - import time - - time.sleep(2) - return np.float32(out_stk) - - -def align_with_tmat(stack_img, tmat_file, transformation=StackReg.TRANSLATION): - - sr = StackReg(transformation) - out_stk = sr.transform_stack(stack_img, tmats=tmat_file) - return np.float32(out_stk) - - -def align_stack_iter( - stack, - ref_stack_void=True, - ref_stack=None, - transformation=StackReg.TRANSLATION, - method=("previous", "first"), - max_iter=2, -): - if ref_stack_void: - ref_stack = stack - - for i in range(max_iter): - sr = StackReg(transformation) - for ii in range(len(method)): - print(ii, method[ii]) - tmats = sr.register_stack(ref_stack, reference=method[ii]) - ref_stack = sr.transform_stack(ref_stack) - stack = sr.transform_stack(stack, tmats=tmats) - - return np.float32(stack) - - -def applyMaskGetMeanSpectrum(im_stack, mask): - """A 2d mask to multiply with the 3d xanes stack and returns mean spectrum""" - - masked_stack = im_stack * mask - return get_mean_spectra(masked_stack) - - -def modifyStack( - raw_stack, - normalizeStack=False, - normToPoint=-1, - applySmooth=False, - smoothWindowSize=3, - applyThreshold=False, - thresholdValue=0, - removeOutliers=False, - nSigmaOutlier=3, - applyTranspose=False, - transposeVals=(0, 1, 2), - applyCrop=False, - cropVals=(0, 1, 2), - removeEdges=False, - resizeStack=False, - upScaling=False, - binFactor=2, -): - - """A giant function to modify the stack with many possible operations. - all the changes can be saved to a jason file as a config file. Enabling and - distabling the sliders is a problem""" - - """ - normStack = normalize(raw_stack, norm_point=normToPoint) - smoothStack = smoothen(raw_stack, w_size= smoothWindowSize) - thresholdStack = clean_stack(raw_stack, auto_bg=False, bg_percentage = thresholdValue) - outlierStack = remove_hot_pixels(raw_stack, NSigma=nSigmaOutlier) - transposeStack = np.transpose(raw_stack, transposeVals) - croppedStack = raw_stack[cropVals] - edgeStack = remove_edges(raw_stack) - binnedStack = resize_stack(raw_stack,upscaling=upScaling,scaling_factor=binFactor) - - """ - - if removeOutliers: - modStack = remove_hot_pixels(raw_stack, NSigma=nSigmaOutlier) - - else: - modStack = raw_stack - - if applyThreshold: - modStack = clean_stack(modStack, auto_bg=False, bg_percentage=thresholdValue) - - else: - pass - - if applySmooth: - modStack = smoothen(modStack, w_size=smoothWindowSize) - - else: - pass - - if applyTranspose: - modStack = np.transpose(modStack, transposeVals) - - else: - pass - - if applyCrop: - modStack = modStack[cropVals] - - else: - pass - - if normalizeStack: - modStack = normalize(raw_stack, norm_point=normToPoint) - else: - pass - - -def start_xmidas(): - def formatter(prog): - # Set maximum width such that printed help mostly fits in the RTD theme code block (documentation). - return argparse.RawDescriptionHelpFormatter(prog, max_help_position=20, width=90) - - parser = argparse.ArgumentParser( - description=f"XMidas: v{__version__}", - formatter_class=formatter, - ) - parser.parse_args() - - logger.setLevel(logging.INFO) - formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(message)s") - stream_handler = logging.StreamHandler() - stream_handler.setFormatter(formatter) - stream_handler.setLevel(logging.INFO) - if logger.hasHandlers(): - logger.handlers.clear() - logger.addHandler(stream_handler) - - if version.parse(PYQT_VERSION_STR) >= version.parse("5.14"): - QApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) - - app = QtWidgets.QApplication(sys.argv) - # app.setAttribute(QtCore.Qt.AA_Use96Dpi) - window = midasWindow() - window.show() - sys.exit(app.exec_()) - - -if __name__ == "__main__": - start_xmidas() +# -*- coding: utf-8 -*- + +# Author: Ajith Pattammattel +# First Version on:06-23-2020 + +import argparse +import logging +import sys +import webbrowser +import traceback +import os +import json +import scipy.stats as stats +import numpy as np +import pandas as pd +import tifffile as tf +import pyqtgraph as pg +import pyqtgraph.exporters +from glob import glob +from pyqtgraph import plot +from itertools import combinations +from scipy.stats import linregress +from packaging import version + +from PyQt5 import QtWidgets, QtCore, QtGui, uic, QtTest +from PyQt5.QtGui import QMovie +from PyQt5.QtWidgets import QMessageBox, QFileDialog, QApplication +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool, PYQT_VERSION_STR + +from utils import * + +#from . import __version__ + +logger = logging.getLogger() +try: + import cv2 # noqa: F401 +except Exception: + logger.warning("openCV module not found") + pass +if hasattr(QtCore.Qt, "AA_EnableHighDpiScaling"): + QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) + +if hasattr(QtCore.Qt, "AA_UseHighDpiPixmaps"): + QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) + +ui_path = os.path.dirname(os.path.abspath(__file__)) + +# global settings for pyqtgraph plot and image colormaps +pg.setConfigOption("imageAxisOrder", "row-major") +cmap_names = ["CET-L13", "CET-L14", "CET-L15"] +cmap_combo = combinations(cmap_names, 2) +cmap_label1 = ["red", "green", "blue"] +cmap_label2 = ["yellow", "magenta", "cyan"] +cmap_dict = {} +for i, name in zip(cmap_names, cmap_label1): + cmap_dict[name] = pg.colormap.get(i).getLookupTable(alpha=True) + +for i, name in zip(cmap_combo, cmap_label2): + cmap_dict[name] = pg.colormap.get(i[0]).getLookupTable(alpha=True) + pg.colormap.get(i[1]).getLookupTable( + alpha=True + ) + cmap_dict[name][:, 3] = 255 + + grey = ( + pg.colormap.get("CET-L13").getLookupTable(alpha=True) + + pg.colormap.get("CET-L14").getLookupTable(alpha=True) + + pg.colormap.get("CET-L15").getLookupTable(alpha=True) + ) + + grey[:, 3] = 255 + cmap_dict["grey"] = grey + + +class jsonEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.integer): + return int(obj) + elif isinstance(obj, np.floating): + return float(obj) + elif isinstance(obj, np.ndarray): + return obj.tolist() + else: + return super(jsonEncoder, self).default(obj) + + +class midasWindow(QtWidgets.QMainWindow): + def __init__(self, im_stack=None, energy=[], refs=[]): + super(midasWindow, self).__init__() + uic.loadUi(os.path.join(ui_path, "uis/midasMainwindow.ui"), self) + self.im_stack = im_stack + self.energy = energy + self.refs = refs + self.loaded_tranform_file = [] + self.image_roi2_flag = False + self.refStackAvailable = False + self.isAReload = False + self.plotWidth = 2 + self.stackStatusDict = {} + + self.user_wd = os.path.expanduser("~") + # self.user_config_path = os.path.join(ui_path,"user_config.json") + + # if not os.path.exists(self.user_config_path): + + # with open(f"{self.user_config_path}", "w") as fp: + # json.dump(self.user_config, fp, indent=4) + + self.plt_colors = [ + "g", + "r", + "c", + "m", + "y", + "w", + "b", + pg.mkPen(70, 5, 80), + pg.mkPen(255, 85, 130), + pg.mkPen(0, 85, 130), + pg.mkPen(255, 170, 60), + ] * 3 + # window style + self.actionDarkMode.triggered.connect(self.darkMode) + self.actionDefault.triggered.connect(self.defaultMode) + self.actionModern.triggered.connect(self.modernMode) + + # self.setToolTipsVisible(True) + for menuItem in self.findChildren(QtWidgets.QMenu): + menuItem.setToolTipsVisible(True) + + # plotview options + self.actionWhite.triggered.connect(lambda: self.spectrum_view.setBackground("w")) + self.actionRed.triggered.connect(lambda: self.spectrum_view.setBackground("r")) + self.actionYellow.triggered.connect(lambda: self.spectrum_view.setBackground("y")) + self.actionBlue.triggered.connect(lambda: self.spectrum_view.setBackground("b")) + self.actionBlack.triggered.connect(lambda: self.spectrum_view.setBackground((0, 0, 0))) + + self.actn1.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn1.text()))) + self.actn2.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn2.text()))) + self.actn3.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn3.text()))) + self.actn4.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn4.text()))) + self.actn5.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn5.text()))) + self.actn6.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn6.text()))) + self.actn8.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn8.text()))) + self.actn10.triggered.connect(lambda: self.setPlotLineWidth(int(self.actn10.text()))) + + self.actionOpen_Image_Data.triggered.connect(self.browse_file) + self.actionOpen_Multiple_Files.triggered.connect(self.createVirtualStack) + self.actionSave_as.triggered.connect(lambda: self.save_stack()) + self.actionExit.triggered.connect(lambda: QApplication.closeAllWindows()) + self.actionOpen_in_GitHub.triggered.connect(self.open_github_link) + self.actionLoad_Energy.triggered.connect(self.select_elist) + self.menuFile.setToolTipsVisible(True) + + # Accessories + self.actionOpen_Mask_Gen.triggered.connect(self.openMaskMaker) + self.actionMultiColor.triggered.connect(self.openMultiColorWindow) + + # calculations + self.pb_transpose_stack.clicked.connect(lambda: self.threadMaker(self.transposeStack)) + self.pb_swapXY_stack.clicked.connect(lambda: self.threadMaker(self.swapStackXY)) + self.pb_reset_img.clicked.connect(self.reloadImageStack) + self.pb_crop.clicked.connect(self.crop_to_dim) + self.pb_apply_crop_to_all.clicked.connect(self.apply_crop_to_all) + self.pb_crop.clicked.connect(self.view_stack) + self.sb_scaling_factor.valueChanged.connect(self.view_stack) + self.pb_ref_xanes.clicked.connect(self.select_ref_file) + self.pb_elist_xanes.clicked.connect(self.select_elist) + + # batchjobs + self.actionPlotAllCorrelations.triggered.connect(self.plotCorrelationsAllCombinations) + + [ + uis.valueChanged.connect(self.replot_image) + for uis in [self.hs_smooth_size, self.hs_nsigma, self.hs_bg_threshold] + ] + + [ + uis.stateChanged.connect(self.replot_image) + for uis in [self.cb_remove_bg, self.cb_remove_outliers, self.cb_smooth, self.cb_norm, self.cb_log] + ] + + [ + uis.stateChanged.connect(self.view_stack) + for uis in [self.cb_remove_edges, self.cb_upscale, self.cb_rebin] + ] + + # ToolBar + self.actionStack_Info.triggered.connect(self.displayStackInfo) + self.actionSave_Image.triggered.connect(self.save_disp_img) + self.actionExport_Stack.triggered.connect(lambda: self.save_stack()) + + # ROI background + self.actionSubtract_ROI_BG.triggered.connect(lambda: self.threadMaker(self.removeROIBGStack)) + + # alignment + self.pb_load_align_ref.clicked.connect(self.loadAlignRefImage) + self.pb_loadAlignTranform.clicked.connect(self.importAlignTransformation) + self.pb_saveAlignTranform.clicked.connect(self.exportAlignTransformation) + self.pb_alignStack.clicked.connect(lambda: self.threadMaker(self.stackRegistration)) + # self.pb_alignStack.clicked.connect(self.stackRegistration) + + # save_options + self.actionSave_Sum_Image.triggered.connect(lambda: self.save_stack(method="sum")) + self.actionSave_Mean_Image.triggered.connect(lambda: self.save_stack(method="mean")) + self.actionExport_Image_to_CSV.triggered.connect(self.stackToCSV) + self.pb_save_disp_spec.clicked.connect(self.save_disp_spec) + self.actionSave_Energy_List.triggered.connect(self.saveEnergyList) + self.pb_show_roi.clicked.connect(self.getROIMask) + self.pb_addToCollector.clicked.connect(self.addSpectrumToCollector) + self.pb_collect_clear.clicked.connect(lambda: self.spectrum_view_collect.clear()) + self.pb_saveCollectorPlot.clicked.connect(self.saveCollectorPlot) + + # XANES Normalization + self.pb_apply_xanes_norm.clicked.connect(self.nomalizeLiveSpec) + self.pb_auto_Eo.clicked.connect(self.findEo) + self.pb_xanes_norm_vals.clicked.connect(self.initNormVals) + self.pb_apply_norm_to_stack.clicked.connect(lambda: self.threadMaker(self.normalizeStack)) + self.actionExport_Norm_Params.triggered.connect(self.exportNormParams) + self.actionImport_Norm_Params.triggered.connect(self.importNormParams) + + # Analysis + self.pb_pca_scree.clicked.connect(self.pca_scree_) + self.pb_calc_components.clicked.connect(self.calc_comp_) + self.pb_kmeans_elbow.clicked.connect(self.kmeans_elbow) + self.pb_calc_cluster.clicked.connect(self.clustering_) + self.pb_xanes_fit.clicked.connect(self.fast_xanes_fitting) + self.pb_plot_refs.clicked.connect(self.plt_xanes_refs) + + self.show() + + self.threadpool = QThreadPool() + print(f"Multithreading with maximum {self.threadpool.maxThreadCount()} threads") + + # View Options + def darkMode(self): + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/darkStyle.css")).read()) + + def defaultMode(self): + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + + def modernMode(self): + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/modern.css")).read()) + + def setPlotLineWidth(self, width_input): + self.plotWidth = width_input + try: + self.update_spectrum() + except Exception: + pass + + def openMultiColorWindow(self): + self.multicolorwindow = MultiChannelWindow() + self.multicolorwindow.show() + + def openMaskMaker(self): + self.mask_window = MaskSpecViewer(xanes_stack=self.displayedStack, energy=self.energy) + self.mask_window.show() + + def open_github_link(self): + webbrowser.open("https://github.com/pattammattel/NSLS-II-MIDAS/wiki") + + def threadMaker(self, funct): + # Pass the function to execute + worker = Worker(funct) # Any other args, kwargs are passed to the run function + self.loadSplashScreen() + worker.signals.start.connect(self.splash.startAnimation) + worker.signals.result.connect(self.print_output) + + list( + map( + worker.signals.finished.connect, + [ + self.thread_complete, + self.splash.stopAnimation, + self.update_stack_info, + self.update_spectrum, + self.update_image_roi, + self.setImageROI + ], + ) + ) + + # Execute + self.threadpool.start(worker) + + + # File Loading + + def createVirtualStack(self): + """User can load multiple/series of tiff images with same shape. + The 'self.load_stack()' recognizes 'self.filename as list and create the stack. + """ + self.energy = [] + filter = "TIFF (*.tiff);;TIF (*.tif);;all_files (*)" + file_name = QFileDialog() + file_name.setFileMode(QFileDialog.ExistingFiles) + names = file_name.getOpenFileNames(self, "Open files", self.user_wd, filter) + if names[0]: + + self.file_name = names[0] + self.user_wd = os.path.dirname(self.file_name) + self.load_stack() + + else: + self.statusbar_main.showMessage("No file has selected") + pass + + def load_stack(self): + + """load the image data from the selected file. + If the the choice is for multiple files stack will be created in a loop. + If single h5 file is selected the unpacking will be done with 'get_xrf_data' function in StackCalcs. + From the h5 the program can recognize the beamline. The exported stack will be normalized to I0. + + If the single tiff file is choosen tf.imread() is used. + + The output 'self.im_stack' is the unmodified data file + """ + + self.log_warning = False # for the Qmessage box in cb_log + self.image_roi2_flag = False + self.cb_log.setChecked(False) + self.cb_remove_edges.setChecked(False) + self.cb_norm.setChecked(False) + self.cb_smooth.setChecked(False) + self.cb_remove_outliers.setChecked(False) + self.cb_remove_bg.setChecked(False) + self.cb_rebin.setChecked(False) + self.cb_upscale.setChecked(False) + self.sb_xrange1.setValue(0) + self.sb_yrange1.setValue(0) + self.sb_zrange1.setValue(0) + + self.menuMask.setEnabled(True) + self.actionLoad_Energy.setEnabled(True) + self.actionSave_Energy_List.setEnabled(True) + self.actionSave_as.setEnabled(True) + + self.sb_zrange2.setMaximum(99999) + self.sb_xrange2.setMaximum(99999) + self.sb_yrange2.setMaximum(99999) + + self.statusbar_main.showMessage("Loading.. please wait...") + + if isinstance(self.file_name, list): + + all_images = [] + + for im_file in self.file_name: + img = tf.imread(im_file) + all_images.append(img) # row major image + self.im_stack = np.dstack(all_images).transpose((2, 0, 1)) + self.avgIo = 1 # I0 is only applicable to XRF h5 files + self.sb_zrange2.setValue(self.im_stack.shape[0]) + + else: + + if self.file_name.endswith(".h5"): + self.im_stack, mono_e, bl_name, self.avgIo = get_xrf_data(self.file_name) + self.statusbar_main.showMessage(f"Data from {bl_name}") + self.sb_zrange2.setValue(mono_e / 10) + self.energy = [] + + elif self.file_name.endswith(".tiff") or self.file_name.endswith(".tif"): + self.im_stack_ = tf.imread(self.file_name) + if self.im_stack_.ndim == 2: + self.im_stack = self.im_stack_.reshape(1, self.im_stack_.shape[0], self.im_stack_.shape[1]) + + else: + self.im_stack = self.im_stack_ + self.sb_zrange2.setValue(self.im_stack.shape[0]) + self.autoEnergyLoader() + self.energyUnitCheck() + self.avgIo = 1 + + else: + logger.error("Unknown data format") + + """ Fill the stack dimensions to the GUI and set the image dimensions as max values. + This prevent user from choosing higher image dimensions during a resizing event""" + + logger.info(f" loaded stack with {np.shape(self.im_stack)} from the file") + + try: + logger.info(f" Transposed to shape: {np.shape(self.im_stack)}") + self.init_dimZ, self.init_dimY, self.init_dimX = self.im_stack.shape + # Remove any previously set max value during a reload + + self.sb_xrange2.setValue(self.init_dimX) + self.sb_yrange2.setValue(self.init_dimY) + + except UnboundLocalError: + logger.error("No file selected") + pass + + self.view_stack() + logger.info("Stack displayed correctly") + self.update_stack_info() + + logger.info(f"completed image shape {np.shape(self.im_stack)}") + + try: + self.statusbar_main.showMessage(f"Loaded: {self.file_name}") + + except AttributeError: + self.statusbar_main.showMessage("New Stack is made from selected tiffs") + pass + + def browse_file(self): + """To open a file widow and choose the data file. + The filename will be used to load data using 'rest and load stack' function""" + + filename = QFileDialog().getOpenFileName( + self, "Select image data", self.user_wd, "image file(*.hdf *.h5 *tiff *tif )" + ) + self.file_name = str(filename[0]) + self.user_wd = os.path.dirname(self.file_name) + + # if user decides to cancel the file window gui returns to original state + if self.file_name: + self.disconnectImageActions() + self.isAReload = False + self.load_stack() + + else: + self.statusbar_main.showMessage("No file has selected") + pass + + def autoEnergyLoader(self): + + dir_, filename_ = os.path.split(self.file_name) + self.efilePath_name = os.path.join(dir_, os.path.splitext(filename_)[0] + ".txt") + self.efilePath_log = os.path.join(dir_, "maps_log_tiff.txt") + + if os.path.isfile(self.efilePath_name): + self.efilePath = self.efilePath_name + self.efileLoader() + self.statusbar_main.showMessage(f"Energy File detected {self.efilePath}") + + elif os.path.isfile(self.efilePath_log): + self.efilePath = self.efilePath_log + self.efileLoader() + self.statusbar_main.showMessage(f"Energy File detected {self.efilePath}") + + else: + self.efilePath = False + self.efileLoader() + + def update_stack_info(self): + z, y, x = np.shape(self.displayedStack) + self.sb_zrange2.setMaximum(z + self.sb_zrange1.value()) + self.sb_xrange2.setValue(x) + self.sb_xrange2.setMaximum(x) + self.sb_yrange2.setValue(y) + self.sb_yrange2.setMaximum(y) + logger.info("Stack info has been updated") + + # Image Transformations + + def crop_to_dim(self): + self.x1, self.x2 = self.sb_xrange1.value(), self.sb_xrange2.value() + self.y1, self.y2 = self.sb_yrange1.value(), self.sb_yrange2.value() + self.z1, self.z2 = self.sb_zrange1.value(), self.sb_zrange2.value() + + try: + self.displayedStack = remove_nan_inf( + self.displayedStack[self.z1 : self.z2, self.y1 : self.y2, self.x1 : self.x2] + ) + except Exception: + self.displayedStack = remove_nan_inf( + self.im_stack[self.z1 : self.z2, self.y1 : self.y2, self.x1 : self.x2] + ) + + def apply_crop_to_all(self): + dir_ = os.path.dirname(self.file_name) + tiffs = glob(dir_+"/*.tiff") + + self.x1, self.x2 = self.sb_xrange1.value(), self.sb_xrange2.value() + self.y1, self.y2 = self.sb_yrange1.value(), self.sb_yrange2.value() + self.z1, self.z2 = self.sb_zrange1.value(), self.sb_zrange2.value() + + print(tiffs) + + for fname in tiffs: + print(fname) + im_array = tf.imread(fname) + im_name = os.path.join(dir_,os.path.basename(fname).split('.')[0]+"_cropped.tiff") + if np.ndim(im_array) == 3: + tf.imwrite(im_name, im_array[self.z1 : self.z2, self.y1 : self.y2, self.x1 : self.x2]) + logger.info(f"{im_name} saved") + elif np.ndim(im_array) == 2: + tf.imwrite(im_name, im_array[self.y1 : self.y2, self.x1 : self.x2]) + logger.info(f"{im_name} saved") + + else: + pass + + + def transpose_stack(self): + self.displayedStack = self.displayedStack.T + self.update_spectrum() + self.update_spec_image_roi() + + # Alignement + + def loadAlignRefImage(self): + filename = QFileDialog().getOpenFileName(self, "Image Data", self.user_wd, "*.tiff *.tif") + file_name = str(filename[0]) + self.user_wd = os.path.dirname(file_name) + self.alignRefImage = tf.imread(file_name) + assert self.alignRefImage.shape == self.displayedStack.shape, "Image dimensions do not match" + self.refStackAvailable = True + self.rb_alignRefVoid.setChecked(False) + self.change_color_on_load(self.pb_load_align_ref) + + def stackRegistration(self): + + self.transformations = { + "TRANSLATION": StackReg.TRANSLATION, + "RIGID_BODY": StackReg.RIGID_BODY, + "SCALED_ROTATION": StackReg.SCALED_ROTATION, + "AFFINE": StackReg.AFFINE, + "BILINEAR": StackReg.BILINEAR, + } + + self.transformType = self.transformations[self.cb_alignTransform.currentText()] + self.alignReferenceImage = self.cb_alignRef.currentText() + self.alignRefStackVoid = self.rb_alignRefVoid.isChecked() + self.alignMaxIter = self.sb_maxIterVal.value() + + if self.cb_use_tmatFile.isChecked(): + + if len(self.loaded_tranform_file) > 0: + + self.displayedStack = align_with_tmat( + self.displayedStack, tmat_file=self.loaded_tranform_file, transformation=self.transformType + ) + logger.info("Aligned to the tranform File") + + else: + logger.error("No Tranformation File Loaded") + + elif self.cb_iterAlign.isChecked(): + + if not self.refStackAvailable: + self.alignRefImage = self.displayedStack + else: + pass + + self.displayedStack = align_stack_iter( + self.displayedStack, + ref_stack_void=False, + ref_stack=self.alignRefImage, + transformation=self.transformType, + method=("previous", "first"), + max_iter=self.alignMaxIter, + ) + + else: + if not self.refStackAvailable: + self.alignRefImage = self.displayedStack + + else: + pass + + self.displayedStack, self.tranform_file = align_stack( + self.displayedStack, + ref_image_void=True, + ref_stack=self.alignRefImage, + transformation=self.transformType, + reference=self.alignReferenceImage, + ) + logger.info("New Tranformation file available") + self.im_stack = self.displayedStack + + def exportAlignTransformation(self): + + + + file_name = QFileDialog().getSaveFileName( + self, + "Save Transformation File", + os.path.join(self.user_wd,"TranformationMatrix.npy"), + "text file (*.npy)" + ) + if file_name[0]: + np.save(file_name[0], self.tranform_file) + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def importAlignTransformation(self): + file_name = QFileDialog().getOpenFileName(self, "Open Transformation File", self.user_wd, "text file (*.npy)") + if file_name[0]: + self.loaded_tranform_file = np.load(file_name[0]) + self.cb_use_tmatFile.setChecked(True) + self.user_wd = os.path.dirname(file_name[0]) + logger.info("Transformation File Loaded") + else: + pass + + def loadSplashScreen(self): + self.splash = LoadingScreen() + + px = self.geometry().x() + py = self.geometry().y() + ph = self.geometry().height() + pw = self.geometry().width() + dw = self.splash.width() + dh = self.splash.height() + new_x, new_y = px + (0.5 * pw) - dw, py + (0.5 * ph) - dh + self.splash.setGeometry(int(new_x), int(new_y), int(dw), int(dh)) + + self.splash.show() + + def reloadImageStack(self): + self.isAReload = True + self.load_stack() + + def update_stack(self): + self.displayedStack = self.im_stack + self.crop_to_dim() + + if self.cb_rebin.isChecked(): + self.cb_upscale.setChecked(False) + self.sb_scaling_factor.setEnabled(True) + self.displayedStack = resize_stack(self.displayedStack, scaling_factor=self.sb_scaling_factor.value()) + self.update_stack_info() + + elif self.cb_upscale.isChecked(): + self.cb_rebin.setChecked(False) + self.sb_scaling_factor.setEnabled(True) + self.displayedStack = resize_stack( + self.displayedStack, upscaling=True, scaling_factor=self.sb_scaling_factor.value() + ) + self.update_stack_info() + + if self.cb_remove_outliers.isChecked(): + self.hs_nsigma.setEnabled(True) + nsigma = self.hs_nsigma.value() / 10 + self.displayedStack = remove_hot_pixels(self.displayedStack, NSigma=nsigma) + self.label_nsigma.setText(str(nsigma)) + logger.info(f"Removing Outliers with NSigma {nsigma}") + + elif self.cb_remove_outliers.isChecked() is False: + self.hs_nsigma.setEnabled(False) + + if self.cb_remove_edges.isChecked(): + self.displayedStack = remove_edges(self.displayedStack) + logger.info(f"Removed edges, new shape {self.displayedStack.shape}") + self.update_stack_info() + + if self.cb_remove_bg.isChecked(): + self.hs_bg_threshold.setEnabled(True) + logger.info("Removing background") + bg_threshold = self.hs_bg_threshold.value() + self.label_bg_threshold.setText(str(bg_threshold) + "%") + self.displayedStack = clean_stack(self.displayedStack, auto_bg=False, bg_percentage=bg_threshold) + + elif self.cb_remove_bg.isChecked() is False: + self.hs_bg_threshold.setEnabled(False) + + if self.cb_log.isChecked(): + + self.displayedStack = remove_nan_inf(np.log10(self.displayedStack)) + logger.info("Log Stack is in use") + + if self.cb_smooth.isChecked(): + self.hs_smooth_size.setEnabled(True) + window = self.hs_smooth_size.value() + if window % 2 == 0: + window = +1 + self.smooth_winow_size.setText("Window size: " + str(window)) + self.displayedStack = smoothen(self.displayedStack, w_size=window) + logger.info("Spectrum Smoothening Applied") + + elif self.cb_smooth.isChecked() is False: + self.hs_smooth_size.setEnabled(False) + + if self.cb_norm.isChecked(): + logger.info("Normalizing spectra") + self.displayedStack = normalize(self.displayedStack, norm_point=-1) + + logger.info("Updated image is in use") + + # ImageView + + def view_stack(self): + + if not self.im_stack.ndim == 3: + raise ValueError("stack should be an ndarray with ndim == 3") + else: + self.update_stack() + # self.StackUpdateThread() + + try: + self.image_view.removeItem(self.image_roi_math) + except Exception: + pass + + (self.dim1, self.dim2, self.dim3) = self.displayedStack.shape + self.image_view.setImage(self.displayedStack) + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + self.image_view.setPredefinedGradient("viridis") + self.image_view.setCurrentIndex(self.dim1 // 2) + if len(self.energy) == 0: + self.energy = np.arange(self.z1, self.z2) * 10 + logger.info("Arbitary X-axis used in the plot for XANES") + self.sz = np.max( + [int(self.dim2 * 0.1), int(self.dim3 * 0.1)] + ) # size of the roi set to be 10% of the image area + + self.stack_center = self.energy[len(self.energy) // 2] + self.stack_width = (self.energy.max() - self.energy.min()) // 10 + self.spec_roi = pg.LinearRegionItem( + values=(self.stack_center - self.stack_width, self.stack_center + self.stack_width) + ) + + # a second optional ROI for calculations follow + self.image_roi_math = pg.PolyLineROI( + [[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], + pos=(int(self.dim3 // 3), int(self.dim2 // 3)), + pen="r", + closed=True, + removable=True, + ) + + self.spec_roi_math = pg.LinearRegionItem( + values=(self.stack_center - self.stack_width - 10, self.stack_center + self.stack_width - 10), + pen="r", + brush=QtGui.QColor(0, 255, 200, 50), + ) + self.spec_lo_m_idx = self.spec_hi_m_idx = 0 + + self.setImageROI() + self.update_spectrum() + self.update_image_roi() + + if not self.isAReload: + # image connections + self.image_view.mousePressEvent = self.getPointSpectrum + self.pb_apply_spec_calc.clicked.connect(self.spec_roi_calc) + self.rb_math_roi.clicked.connect(self.update_spectrum) + self.pb_add_roi_2.clicked.connect(self.math_img_roi_flag) + self.image_roi_math.sigRegionChangeFinished.connect(self.image_roi_calc) + self.pb_apply_img_calc.clicked.connect(self.image_roi_calc) + + self.spec_roi.sigRegionChanged.connect(self.update_image_roi) + self.spec_roi_math.sigRegionChangeFinished.connect(self.spec_roi_calc) + + [ + rbs.clicked.connect(self.setImageROI) + for rbs in [self.rb_poly_roi, self.rb_elli_roi, self.rb_rect_roi, self.rb_line_roi, self.rb_circle_roi] + ] + + def disconnectImageActions(self): + for btns in [self.pb_apply_spec_calc, self.rb_math_roi, self.pb_add_roi_2, self.pb_apply_img_calc]: + try: + btns.disconnect() + except Exception: + pass + + def select_elist(self): + self.energyFileChooser() + self.efileLoader() + self.energyUnitCheck() + self.view_stack() + + def efileLoader(self): + + if self.efilePath: + + if str(self.efilePath).endswith("log_tiff.txt"): + self.energy = energy_from_logfile(logfile=str(self.efilePath)) + logger.info("Log file from pyxrf processing") + + else: + self.energy = np.loadtxt(str(self.efilePath)) + self.change_color_on_load(self.pb_elist_xanes) + logger.info("Energy file loaded") + + else: + self.statusbar_main.showMessage("No Energy List Selected, Setting Arbitary Axis") + self.energy = np.arange(self.im_stack.shape[0]) + logger.info("Arbitary Energy Axis") + + # assert len(self.energy) == self.dim1, "Number of Energy Points is not equal to stack length" + + def energyUnitCheck(self): + + if np.max(self.energy) < 100: + self.cb_kev_flag.setChecked(True) + self.energy *= 1000 + + else: + self.cb_kev_flag.setChecked(False) + + def select_ref_file(self): + self.pb_xanes_fit.setEnabled(True) + self.ref_names = [] + file_name = QFileDialog().getOpenFileName(self, "Open reference file", self.user_wd, "text file (*.csv *.nor)") + if file_name[0]: + if file_name[0].endswith(".nor"): + self.refs, self.ref_names = create_df_from_nor_try2(athenafile=str(file_name[0])) + self.change_color_on_load(self.pb_ref_xanes) + + elif file_name[0].endswith(".csv"): + self.refs = pd.read_csv(str(file_name[0])) + self.ref_names = list(self.refs.keys()) + + self.change_color_on_load(self.pb_ref_xanes) + + self.user_wd = os.path.dirname(file_name[0]) + + else: + logger.error("No file selected") + pass + + logger.info(f"{self.refs.shape = }") + + self.plt_xanes_refs() + + def plt_xanes_refs(self): + + try: + self.ref_plot.close() + except Exception: + pass + + self.ref_plot = plot(title="Reference Standards") + self.ref_plot.setLabel("bottom", "Energy") + self.ref_plot.setLabel("left", "Intensity") + self.ref_plot.addLegend() + + for n in range(np.shape(self.refs)[1]): + + if not n == 0: + self.ref_plot.plot( + self.refs.values[:, 0], + self.refs.values[:, n], + pen=pg.mkPen(self.plt_colors[n - 1], width=self.plotWidth), + name=self.ref_names[n], + ) + + def getPointSpectrum(self, event): + if event.type() == QtCore.QEvent.MouseButtonDblClick: + if event.button() == QtCore.Qt.LeftButton: + self.xpixel = int(self.image_view.view.mapSceneToView(event.pos()).x()) - 1 + zlim, ylim, xlim = self.displayedStack.shape + + if self.xpixel > xlim: + self.xpixel = xlim - 1 + + self.ypixel = int(self.image_view.view.mapSceneToView(event.pos()).y()) - 1 + if self.ypixel > ylim: + self.ypixel = ylim - 1 + self.spectrum_view.addLegend() + self.point_spectrum = self.displayedStack[:, self.ypixel, self.xpixel] + self.spectrum_view.plot( + self.xdata, + self.point_spectrum, + clear=True, + pen=pg.mkPen(pg.mkColor(0, 0, 255, 255), width=self.plotWidth), + symbol="o", + symbolSize=6, + symbolBrush="r", + name=f"Point Spectrum; x= {self.xpixel}, y= {self.ypixel}", + ) + + self.spectrum_view.addItem(self.spec_roi) + + self.statusbar_main.showMessage(f"{self.xpixel} and {self.ypixel}") + + def setImageROI(self): + + self.lineROI = pg.LineSegmentROI([[int(self.dim3 // 2), int(self.dim2 // 2)], [self.sz, self.sz]], pen="r") + + self.rectROI = pg.RectROI( + [int(self.dim3 // 2), int(self.dim2 // 2)], + [self.sz, self.sz], + pen="w", + maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), + ) + + self.rectROI.addTranslateHandle([0, 0], [2, 2]) + self.rectROI.addRotateHandle([0, 1], [2, 2]) + + self.ellipseROI = pg.EllipseROI( + [int(self.dim3 // 2), int(self.dim2 // 2)], + [self.sz, self.sz], + pen="w", + maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), + ) + + self.circleROI = pg.CircleROI( + [int(self.dim3 // 2), int(self.dim2 // 2)], + [self.sz, self.sz], + pen="w", + maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), + ) # pos and size + + self.polyLineROI = pg.PolyLineROI( + [[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], + pos=(int(self.dim3 // 2), int(self.dim2 // 2)), + maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), + closed=True, + removable=True, + ) + + self.rois = { + "rb_line_roi": self.lineROI, + "rb_rect_roi": self.rectROI, + "rb_circle_roi": self.circleROI, + "rb_elli_roi": self.ellipseROI, + "rb_poly_roi": self.polyLineROI, + } + + button_name = self.sender() + + if button_name.objectName() in self.rois.keys(): + self.roi_preference = button_name.objectName() + + else: + self.roi_preference = "rb_rect_roi" # default + + try: + self.image_view.removeItem(self.image_roi) + + except Exception: + pass + + # ROI settings for image, used polyline roi with non rectangular shape + + self.image_roi = self.rois[self.roi_preference] + self.image_view.addItem(self.image_roi) + self.image_roi.sigRegionChanged.connect(self.update_spectrum) + + def replot_image(self): + self.update_stack() + self.update_spectrum() + self.update_image_roi() + + def update_spec_roi_values(self): + self.stack_center = int(self.energy[len(self.energy) // 2]) + self.stack_width = int((self.energy.max() - self.energy.min()) * 0.05) + self.spec_roi.setBounds([self.xdata[0], self.xdata[-1]]) # if want to set bounds for the spec roi + self.spec_roi_math.setBounds([self.xdata[0], self.xdata[-1]]) + + def update_spectrum(self): + + # set x-axis values; array taken from energy values, if clipped z box values will update the array + self.xdata = self.energy[self.sb_zrange1.value() : self.sb_zrange2.value()] + + # get the cropped stack from ROI region; pyqtgraph function is used + self.roi_img_stk = self.image_roi.getArrayRegion( + self.displayedStack, self.image_view.imageItem, axes=(1, 2) + ) + + posx, posy = self.image_roi.pos() + self.le_roi.setText(str(int(posx)) + ":" + str(int(posy))) + + # display the ROI features in the line edit boxes + if self.roi_img_stk.ndim == 3: + sizex, sizey = self.roi_img_stk.shape[1], self.roi_img_stk.shape[2] + self.le_roi_size.setText(str(sizex) + "," + str(sizey)) + self.mean_spectra = get_mean_spectra(self.roi_img_stk) + + elif self.roi_img_stk.ndim == 2: + sizex, sizey = self.roi_img_stk.shape[0], self.roi_img_stk.shape[1] + self.le_roi_size.setText(str(sizex) + "," + str(sizey)) + self.mean_spectra = self.roi_img_stk.mean(-1) + + self.spectrum_view.addLegend() + + try: + self.spectrum_view.plot( + self.xdata, + self.mean_spectra, + pen=pg.mkPen(pg.mkColor(5, 255, 5, 255), width=self.plotWidth), + clear=True, + symbol="o", + symbolSize=6, + symbolBrush="r", + name="ROI Spectrum", + ) + except Exception: + self.spectrum_view.plot( + self.mean_spectra, + clear=True, + pen=pg.mkPen(pg.mkColor(5, 255, 5, 255), width=self.plotWidth), + symbol="o", + symbolSize=6, + symbolBrush="r", + name="ROI Spectrum", + ) + + if self.energy[-1] > 1000: + self.e_unit = "eV" + else: + self.e_unit = "keV" + + self.spectrum_view.setLabel("bottom", "Energy", self.e_unit) + self.spectrum_view.setLabel("left", "Intensity", "A.U.") + self.spectrum_view.addItem(self.spec_roi) + self.update_spec_roi_values() + self.math_roi_flag() + + def update_image_roi(self): + self.spec_lo, self.spec_hi = self.spec_roi.getRegion() + self.spec_lo_idx = (np.abs(self.energy - self.spec_lo)).argmin() + self.spec_hi_idx = (np.abs(self.energy - self.spec_hi)).argmin() + self.le_spec_roi.setText(str(int(self.spec_lo)) + ":" + str(int(self.spec_hi))) + self.le_spec_roi_size.setText(str(int(self.spec_hi - self.spec_lo))) + self.update_spec_roi_values() + self.stackIndexToNames() + + try: + if int(self.spec_lo_idx) == int(self.spec_hi_idx): + self.disp_img = self.displayedStack[int(self.spec_hi_idx), :, :] + + else: + self.disp_img = self.displayedStack[int(self.spec_lo_idx) : int(self.spec_hi_idx), :, :].mean(0) + + self.image_view.setImage(self.disp_img) + self.statusbar_main.showMessage(f"Image Display is {self.corrImg1}") + except Exception: + logger.warning("Indices are out of range; Image cannot be created") + pass + + def set_spec_roi(self): + self.spec_lo_, self.spec_hi_ = int(self.sb_roi_spec_s.value()), int(self.sb_roi_spec_e.value()) + self.spec_lo_idx_ = (np.abs(self.energy - self.spec_lo_)).argmin() + self.spec_hi_idx_ = (np.abs(self.energy - self.spec_hi_)).argmin() + self.spec_roi.setRegion((self.xdata[self.spec_lo_idx_], self.xdata[self.spec_hi_idx_])) + self.update_image_roi() + + def math_roi_flag(self): + if self.rb_math_roi.isChecked(): + self.rb_math_roi.setStyleSheet("color : green") + self.spectrum_view.addItem(self.spec_roi_math) + else: + self.spectrum_view.removeItem(self.spec_roi_math) + + def spec_roi_calc(self): + + self.spec_lo_m, self.spec_hi_m = self.spec_roi_math.getRegion() + self.spec_lo_m_idx = (np.abs(self.energy - self.spec_lo_m)).argmin() + self.spec_hi_m_idx = (np.abs(self.energy - self.spec_hi_m)).argmin() + + if int(self.spec_lo_idx) == int(self.spec_hi_idx): + self.img1 = self.displayedStack[int(self.spec_hi_idx), :, :] + + else: + self.img1 = self.displayedStack[int(self.spec_lo_idx) : int(self.spec_hi_idx), :, :].mean(0) + + if int(self.spec_lo_m_idx) == int(self.spec_hi_m_idx): + self.img2 = self.displayedStack[int(self.spec_hi_m_idx), :, :] + + else: + self.img2 = self.displayedStack[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx), :, :].mean(0) + + if self.cb_roi_operation.currentText() == "Correlation Plot": + self.correlation_plot() + + else: + calc = {"Divide": np.divide, "Subtract": np.subtract, "Add": np.add} + self.disp_img = remove_nan_inf(calc[self.cb_roi_operation.currentText()](self.img1, self.img2)) + self.image_view.setImage(self.disp_img) + + def math_img_roi_flag(self): + + button_name = self.sender().text() + logger.info(f"{button_name}") + if button_name == "Add ROI_2": + self.image_view.addItem(self.image_roi_math) + self.pb_add_roi_2.setText("Remove ROI_2") + self.image_roi2_flag = 1 + elif button_name == "Remove ROI_2": + self.image_view.removeItem(self.image_roi_math) + self.pb_add_roi_2.setText("Add ROI_2") + self.image_roi2_flag = 0 + + else: + pass + logger.error("Unknown signal for second ROI") + + def image_roi_calc(self): + + if self.image_roi2_flag == 1: + self.calc = {"Divide": np.divide, "Subtract": np.subtract, "Add": np.add} + self.update_spec_image_roi() + else: + logger.error("No ROI2 found") + return + + def update_spec_image_roi(self): + + self.math_roi_reg = self.image_roi_math.getArrayRegion( + self.displayedStack, self.image_view.imageItem, axes=(1, 2) + ) + if self.math_roi_reg.ndim == 3: + + self.math_roi_spectra = get_mean_spectra(self.math_roi_reg) + + elif self.roi_img_stk.ndim == 2: + self.math_roi_spectra = self.math_roi_reg.mean(-1) + + if self.cb_img_roi_action.currentText() in self.calc.keys(): + + calc_spec = self.calc[self.cb_img_roi_action.currentText()](self.mean_spectra, self.math_roi_spectra) + self.spectrum_view.addLegend() + self.spectrum_view.plot( + self.xdata, + calc_spec, + clear=True, + pen=pg.mkPen("m", width=2), + name=self.cb_img_roi_action.currentText() + "ed", + ) + self.spectrum_view.plot(self.xdata, self.math_roi_spectra, pen=pg.mkPen("y", width=2), name="ROI2") + self.spectrum_view.plot(self.xdata, self.mean_spectra, pen=pg.mkPen("g", width=2), name="ROI1") + + elif self.cb_img_roi_action.currentText() == "Compare": + self.spectrum_view.plot( + self.xdata, self.math_roi_spectra, pen=pg.mkPen("y", width=2), clear=True, name="ROI2" + ) + self.spectrum_view.plot(self.xdata, self.mean_spectra, pen=pg.mkPen("g", width=2), name="ROI1") + + self.spectrum_view.addItem(self.spec_roi) + + def displayStackInfo(self): + + try: + + if isinstance(self.file_name, list): + info = f"Folder; {os.path.dirname(self.file_name[0])} \n" + for n, name in enumerate(self.file_name): + info += f"{n}: {os.path.basename(name)} \n" + + # info = f'Stack order; {[name for name in enumerate(self.file_name)]}' + else: + info = f"Stack; {self.file_name}" + + self.infoWindow = StackInfo(str(info)) + self.infoWindow.show() + + except AttributeError: + self.statusbar_main.showMessage("Warning: No Image Data Loaded") + + def stackIndexToNames(self): + # create list of tiff file names for virtutal stack for plot axes + self.elemFileName = [] + + if isinstance(self.file_name, list): + for name in self.file_name: + self.elemFileName.append(os.path.basename(name).split(".")[0]) + + logger.info(f" Virtual Stack - list of image names; {self.elemFileName}") + + # if the roi focus on one frame, Note that this slicing excludes the last index + if int(self.spec_lo_idx) == int(self.spec_hi_idx): + self.corrImg1 = str(self.elemFileName[int(self.spec_lo_idx)]) + else: + self.corrImg1 = self.elemFileName[int(self.spec_lo_idx) : int(self.spec_hi_idx)] + if len(self.corrImg1) > 1: + self.corrImg1 = f"Sum of {self.corrImg1} " + + if int(self.spec_lo_m_idx) == int(self.spec_hi_m_idx): + self.corrImg2 = str(self.elemFileName[int(self.spec_lo_m_idx)]) + + else: + self.corrImg2 = self.elemFileName[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx)] + + if len(self.corrImg2) > 1: + self.corrImg2 = f"Sum of {self.corrImg2}" + + logger.info( + f"Correlation stack {int(self.spec_lo_idx)}:{int(self.spec_hi_idx)} with " + f"{int(self.spec_lo_m_idx)}:{int(self.spec_hi_m_idx)}" + ) + + logger.info(f" Virtual Stack; corrlation plot of {self.corrImg1} vs {self.corrImg2}") + else: + self.corrImg1 = ( + f" Sum of {os.path.basename(self.file_name).split('.')[0]}_{int(self.spec_lo_idx)} " + f"to {int(self.spec_hi_idx)}" + ) + self.corrImg2 = ( + f" Sum of {os.path.basename(self.file_name).split('.')[0]}_{int(self.spec_lo_m_idx)} " + f"to {int(self.spec_hi_m_idx)}" + ) + # logger.info(f" corrlation plot of {self.corrImg1} vs {self.corrImg2}") + + def stackToCSV(self): + + self.stackIndexToNames() + self.imageDf = pd.DataFrame() + if len(self.elemFileName) == len(self.displayedStack): + for name, image in zip(self.elemFileName, self.displayedStack): + self.imageDf[f'{name}'] = image.flatten() + # print(self.imageDf.head()) + else: + self.imageDf = image_to_pandas2(self.displayedStack) + + file_name = QFileDialog().getSaveFileName(self, + "Save CSV Data", + os.path.join(self.user_wd,'image_2DArray.csv'), + 'file (*csv)') + if file_name[0]: + self.imageDf.to_csv(path_or_buf=file_name[0]) + self.user_wd = os.path.dirname(file_name[0]) + self.statusbar_main.showMessage(f"Data saved to {str(file_name[0])}") + else: + pass + + def correlation_plot(self): + self.stackIndexToNames() + + self.statusbar_main.showMessage(f"Correlation of {self.corrImg1} with {self.corrImg2}") + + if self.rb_roiRegionOnly.isChecked(): + self.roi_mask = self.image_roi.getArrayRegion( + self.displayedStack, self.image_view.imageItem, axes=(1, 2) + ) + self.roi_img1 = np.mean(self.roi_mask[int(self.spec_lo_idx) : int(self.spec_hi_idx)], axis=0) + self.roi_img2 = np.mean(self.roi_mask[int(self.spec_lo_m_idx) : int(self.spec_hi_m_idx)], axis=0) + self.scatter_window = ScatterPlot( + self.roi_img1, self.roi_img2, (str(self.corrImg1), str(self.corrImg2)) + ) + + else: + + self.scatter_window = ScatterPlot(self.img1, self.img2, (str(self.corrImg1), str(self.corrImg2))) + + self.scatter_window.show() + + def plotCorrelationsAllCombinations(self): + + print("Plotting all correlations") + self.stackIndexToNames() + allElemCombNum = list(combinations(np.arange(len(self.elemFileName)), 2)) + + self.scW1 = self.scW2 = self.scW3 = self.scW4 = self.scW5 = None + self.scW6 = self.scW7 = self.scW8 = self.scW9 = self.scW10 = None + + self.scWindowList = [ + self.scW1, + self.scW2, + self.scW3, + self.scW4, + self.scW5, + self.scW6, + self.scW7, + self.scW8, + self.scW9, + self.scW10, + ] + self.scWindowDict = { + 1: self.scW1, + 2: self.scW2, + 3: self.scW3, + 4: self.scW4, + 5: self.scW5, + 6: self.scW6, + 7: self.scW7, + 8: self.scW8, + 9: self.scW9, + 10: self.scW10, + } + + if len(allElemCombNum) > len(self.scWindowDict): + + reply = QMessageBox.warning( + self, + "Plot Window Limit", + f"The number of combination exceeds " + f"maxiumum number of " + f"plot windows. First {len(self.scWindowDict)} " + f"combinations will be plotted. \n Proceed?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) + + if reply == QMessageBox.Yes: + + for i, pair in enumerate(allElemCombNum): + im1 = self.displayedStack[pair[0]] + im2 = self.displayedStack[pair[1]] + im1Name = self.elemFileName[pair[0]] + im2Name = self.elemFileName[pair[1]] + + self.scWindowDict[i] = ScatterPlot(im1, im2, (str(im1Name), str(im2Name))) + self.scWindowDict[i].show() + + if reply == QMessageBox.No: + return + + else: + + for i, pair in enumerate(allElemCombNum): + im1 = self.displayedStack[pair[0]] + im2 = self.displayedStack[pair[1]] + im1Name = self.elemFileName[pair[0]] + im2Name = self.elemFileName[pair[1]] + + self.scWindowDict[i] = ScatterPlot(im1, im2, (str(im1Name), str(im2Name))) + self.scWindowDict[i].show() + + def getROIMask(self): + self.roi_mask = self.image_roi.getArrayRegion(self.displayedStack, self.image_view.imageItem, axes=(1, 2)) + self.newWindow = singleStackViewer(self.roi_mask) + self.newWindow.show() + + def save_stack(self, method="raw"): + + # self.update_stack() + file_name = QFileDialog().getSaveFileName( + self, + "Save image data", + os.path.join(self.user_wd,"image_data.tiff"), + "image file(*tiff *tif )" + ) + if file_name[0]: + if method == "raw": + + tf.imsave(str(file_name[0]), self.displayedStack) + logger.info(f"Updated Image Saved: {str(file_name[0])}") + self.statusbar_main.showMessage(f"Updated Image Saved: {str(file_name[0])}") + elif method == "sum": + tf.imsave(str(file_name[0]), np.sum(self.displayedStack, axis=0)) + + elif method == "mean": + tf.imsave(str(file_name[0]), np.mean(self.displayedStack, axis=0)) + + self.user_wd = os.path.dirname(file_name[0]) + + else: + self.statusbar_main.showMessage("Saving cancelled") + logger.info(f"Save failed: {str(file_name[0])}") + pass + + def save_disp_img(self): + file_name = QFileDialog().getSaveFileName(self, + "Save image data", + os.path.join(self.user_wd,"image.tiff"), + "image file(*tiff *tif )") + if file_name[0]: + tf.imsave(str(file_name[0]), self.disp_img) + self.statusbar_main.showMessage(f"Image Saved to {str(file_name[0])}") + self.user_wd = os.path.dirname(file_name[0]) + logger.info(f"Updated Image Saved: {str(file_name[0])}") + + else: + logger.error("No file to save") + self.statusbar_main.showMessage("Saving cancelled") + pass + + def getLivePlotData(self): + try: + + data = np.squeeze([c.getData() for c in self.spectrum_view.plotItem.curves]) + # print(np.shape(data)) + if data.ndim == 2: + self.mu_ = data[1] + self.e_ = data[0] + elif data.ndim == 3: + e_mu = data[0, :, :] + self.mu_ = e_mu[1] + self.e_ = e_mu[0] + + else: + logger.error(f" Data shape of {data.ndim} is not supported") + pass + except AttributeError: + logger.error("No data loaded") + pass + + def addSpectrumToCollector(self): + self.getLivePlotData() + self.spectrum_view_collect.plot(self.e_, self.mu_, name="ROI Spectrum") + self.spectrum_view_collect.setLabel("bottom", "Energy", self.e_unit) + self.spectrum_view_collect.setLabel("left", "Intensity", "A.U.") + + def findEo(self): + try: + self.getLivePlotData() + e0_init = self.e_[np.argmax(np.gradient(self.mu_))] + self.dsb_norm_Eo.setValue(e0_init) + + except AttributeError: + logger.error("No data loaded") + pass + + def initNormVals(self): + self.getLivePlotData() + e0_init = self.e_[np.argmax(np.gradient(self.mu_))] + pre1, pre2, post1, post2 = xanesNormalization( + self.e_, + self.mu_, + e0=e0_init, + step=None, + nnorm=1, + nvict=0, + method= "guess" + ) + self.dsb_norm_pre1.setValue(pre1) + self.dsb_norm_pre2.setValue(pre2) + self.dsb_norm_post1.setValue(post1) + self.dsb_norm_post2.setValue(post2) + self.dsb_norm_Eo.setValue(e0_init) + + def getNormParams(self): + self.getLivePlotData() + eo_ = self.dsb_norm_Eo.value() + pre1_, pre2_ = self.dsb_norm_pre1.value(), self.dsb_norm_pre2.value() + norm1_, norm2_ = self.dsb_norm_post1.value(), self.dsb_norm_post2.value() + norm_order = self.sb_norm_order.value() + + return eo_, pre1_, pre2_, norm1_, norm2_, norm_order + + def exportNormParams(self): + self.xanesNormParam = {} + eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() + self.xanesNormParam["E0"] = eo_ + self.xanesNormParam["pre1"] = pre1_ + self.xanesNormParam["pre2"] = pre2_ + self.xanesNormParam["post1"] = norm1_ + self.xanesNormParam["post2"] = norm2_ + self.xanesNormParam["norm_order"] = norm_order + + file_name = QtWidgets.QFileDialog().getSaveFileName( + self, + "Save XANES Norm Params", + os.path.join(self.user_wd,"xanes_norm_params.csv"), + "csv file(*csv)" + ) + if file_name[0]: + pd.DataFrame(self.xanesNormParam, index=[0]).to_csv(file_name[0]) + self.user_wd = os.path.dirname(file_name[0]) + + else: + pass + + def importNormParams(self): + + file_name = QtWidgets.QFileDialog().getOpenFileName( + self, "Open a XANES Norm File", self.user_wd, "csv file(*csv);;all_files (*)" + ) + + if file_name[0]: + xanesNormParam = pd.read_csv(file_name[0]) + self.dsb_norm_Eo.setValue(xanesNormParam["E0"]) + self.dsb_norm_pre1.setValue(xanesNormParam["pre1"]) + self.dsb_norm_pre2.setValue(xanesNormParam["pre2"]) + self.dsb_norm_post1.setValue(xanesNormParam["post1"]) + self.dsb_norm_post2.setValue(xanesNormParam["post2"]) + self.sb_norm_order.setValue(xanesNormParam["norm_order"]) + self.user_wd = os.path.dirname(file_name[0]) + + def nomalizeLiveSpec(self): + eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() + self.spectrum_view.clear() + colors = np.array(("c", "r", "m")) + + if self.cb_mback.isChecked(): + f2, normXANES = xanesNormalization( + self.e_, + self.mu_, + e0=eo_, + step=None, + nnorm=norm_order, + nvict=0, + pre1=pre1_, + pre2=pre2_, + norm1=norm1_, + norm2=norm2_, + useFlattened=self.cb_xanes_flat.isChecked(), + method = "mback" + ) + + names = np.array(("matched mu(E)", "f2")) + data_array = np.array((normXANES, f2)) + + else: + pre_line, post_line, normXANES = xanesNormalization( + self.e_, + self.mu_, + e0=eo_, + step=None, + nnorm=norm_order, + nvict=0, + pre1=pre1_, + pre2=pre2_, + norm1=norm1_, + norm2=norm2_, + useFlattened=self.cb_xanes_flat.isChecked() + ) + + names = np.array(("Spectrum", "Pre", "Post")) + data_array = np.array((self.mu_, pre_line, post_line)) + + + for data, clr, name in zip(data_array, colors, names): + self.spectrum_view.plot(self.e_, data, pen=pg.mkPen(clr, width=self.plotWidth), name=name) + + self.spectrum_view_norm.plot( + self.e_, normXANES, clear=True, pen=pg.mkPen(self.plt_colors[-1], width=self.plotWidth)) + + self.spectrum_view_norm.setLabel("bottom", "Energy", self.e_unit) + self.spectrum_view_norm.setLabel("left", "Norm. Intensity", "A.U.") + + def normalizeStack(self): + self.getLivePlotData() + eo_, pre1_, pre2_, norm1_, norm2_, norm_order = self.getNormParams() + + self.im_stack = self.displayedStack = xanesNormStack( + self.e_, + self.displayedStack, + e0=eo_, + step=None, + nnorm=norm_order, + nvict=0, + pre1=pre1_, + pre2=pre2_, + norm1=norm1_, + norm2=norm2_, + useFlattened=self.cb_xanes_flat.isChecked(), + ignorePostEdgeNorm=self.cb_xanes_postedge.isChecked() + ) + # self.im_stack = self.displayedStack + + def transposeStack(self): + self.im_stack = self.displayedStack = np.transpose(self.displayedStack, (2, 1, 0)) + + def swapStackXY(self): + self.im_stack = self.displayedStack = np.transpose(self.displayedStack, (0, 2, 1)) + + def removeROIBGStack(self): + self.displayedStack = subtractBackground(self.displayedStack, self.mean_spectra) + + def resetCollectorSpec(self): + pass + + def saveCollectorPlot(self): + exporter = pg.exporters.CSVExporter(self.spectrum_view_collect.plotItem) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, "save spectra", self.user_wd, "spectra (*csv)") + if file_name[0]: + exporter.export(str(file_name[0]) + ".csv") + self.user_wd = os.path.dirname(file_name[0]) + else: + self.statusbar_main.showMessage("Saving cancelled") + pass + + def save_disp_spec(self): + + exporter = pg.exporters.CSVExporter(self.spectrum_view.plotItem) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, + "save spectrum", + os.path.join(self.user_wd,"spectrum.csv"), + "spectra (*csv)") + if file_name[0]: + exporter.export(str(file_name[0]) + ".csv") + self.user_wd = os.path.dirname(file_name[0]) + else: + self.statusbar_main.showMessage("Saving cancelled") + pass + + def saveEnergyList(self): + file_name = QFileDialog().getSaveFileName(self, + "save energy list", + os.path.join(self.user_wd,"energy_list.txt"), + "text file (*txt)") + if file_name[0]: + np.savetxt(file_name[0], self.xdata, fmt="%.4f") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def pca_scree_(self): + logger.info("Process started..") + self.update_stack() + var = pca_scree(self.displayedStack) + + pca_scree_plot = pg.plot( + var[:24], title="Scree Plot", pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), symbol="o" + ) + pca_scree_plot.addLine(y=0) + pca_scree_plot.setLabel("bottom", "Component Number") + pca_scree_plot.setLabel("left", "Singular Values") + + logger.info("Process complete") + + def calc_comp_(self): + + logger.info("Process started..") + + # self.update_stack() + n_components = self.sb_ncomp.value() + method_ = self.cb_comp_method.currentText() + + ims, comp_spec, decon_spec, decomp_map = decompose_stack( + self.displayedStack, decompose_method=method_, n_components_=n_components + ) + + self._new_window3 = ComponentViewer(ims, self.xdata, comp_spec, decon_spec, decomp_map) + self._new_window3.show() + + logger.info("Process complete") + + def kmeans_elbow(self): + logger.info("Process started..") + # self.update_stack() + + with pg.BusyCursor(): + try: + clust_n, var = kmeans_variance(self.displayedStack) + kmeans_var_plot = pg.plot( + clust_n, var, title="KMeans Variance", pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), + symbol="o" + ) + kmeans_var_plot.setLabel("bottom", "Cluster Number") + kmeans_var_plot.setLabel("left", "Sum of squared distances") + logger.info("Process complete") + except OverflowError: + pass + logger.error("Overflow Error, values are too long") + + def kmeans_elbow_Thread(self): + # Pass the function to execute + worker = Worker(self.kmeans_elbow) # Any other args, kwargs are passed to the run function + worker.signals.result.connect(self.print_output) + worker.signals.finished.connect(self.thread_complete) + # Execute + self.threadpool.start(worker) + + def clustering_(self): + + logger.info("Process started..") + # self.update_stack() + method_ = self.cb_clust_method.currentText() + + decon_images, X_cluster, decon_spectra = cluster_stack( + self.displayedStack, + method=method_, + n_clusters_=self.sb_ncluster.value(), + decomposed=False, + decompose_method=self.cb_comp_method.currentText(), + decompose_comp=self.sb_ncomp.value(), + ) + + self._new_window4 = ClusterViewer(decon_images, self.xdata, X_cluster, decon_spectra) + self._new_window4.show() + + logger.info("Process complete") + + def change_color_on_load(self, button_name): + button_name.setStyleSheet("background-color : rgb(0,150,0);" "color: rgb(255,255,255)") + + def energyFileChooser(self): + file_name = QFileDialog().getOpenFileName(self, + "Open energy list", + self.user_wd, + "text file (*.txt)") + self.efilePath = file_name[0] + + def fast_xanes_fitting(self): + + self._new_window5 = XANESViewer(self.displayedStack, self.xdata, self.refs, self.ref_names) + self._new_window5.show() + + # Thread Signals + + def print_output(self, s): + print(s) + + def thread_complete(self): + print("THREAD COMPLETE!") + + def closeEvent(self, event): + reply = QMessageBox.question( + self, + "Window Close", + "Are you sure you want to close?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) + + if reply == QMessageBox.Yes: + event.accept() + QApplication.closeAllWindows() + else: + event.ignore() + + +class WorkerSignals(QObject): + """ + Defines the signals available from a running worker thread. + Supported signals are: + - finished: No data + - error:`tuple` (exctype, value, traceback.format_exc() ) + - result: `object` data returned from processing, anything + - progress: `tuple` indicating progress metadata + """ + + start = pyqtSignal() + finished = pyqtSignal() + error = pyqtSignal(tuple) + result = pyqtSignal(object) + + +class Worker(QRunnable): + """ + Worker thread + Inherits from QRunnable to handler worker thread setup, signals and wrap-up. + """ + + def __init__(self, fn, *args, **kwargs): + super(Worker, self).__init__() + # Store constructor arguments (re-used for processing) + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + @pyqtSlot() + def run(self): + """ + Initialise the runner function with passed args, kwargs. + """ + # Retrieve args/kwargs here; and fire processing using them + self.signals.start.emit() + try: + result = self.fn(*self.args, **self.kwargs) + except Exception: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) # Return the result of the processing + finally: + self.signals.finished.emit() # Done + + +class singleStackViewer(QtWidgets.QMainWindow): + def __init__(self, img_stack, gradient="viridis"): + super(singleStackViewer, self).__init__() + + # Load the UI Page + uic.loadUi(os.path.join(ui_path, "uis/singleStackView.ui"), self) + self.user_wd = os.path.abspath("~") + + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + + self.img_stack = img_stack + self.gradient = gradient + self.image_view.setPredefinedGradient(gradient) + + if self.img_stack.ndim == 3: + self.dim1, self.dim3, self.dim2 = img_stack.shape + elif self.img_stack.ndim == 2: + self.dim3, self.dim2 = img_stack.shape + self.dim1 = 1 + self.hs_img_stack.setMaximum(self.dim1 - 1) + self.hs_img_stack.setValue(np.round(self.dim1 / 2)) + self.displayStack() + + # connections + self.hs_img_stack.valueChanged.connect(self.displayStack) + self.actionSave.triggered.connect(self.saveImageStackAsTIFF) + + def displayStack(self): + im_index = self.hs_img_stack.value() + if self.img_stack.ndim == 2: + self.image_view.setImage(self.img_stack) + else: + self.image_view.setImage(self.img_stack[im_index]) + self.label_img_count.setText(f"{im_index + 1}/{self.dim1}") + + def saveImageStackAsTIFF(self): + file_name = QFileDialog().getSaveFileName(self, + "Export Stack", + os.path.join(self.user_wd, "image_stack.tiff"), + "*.tiff;;*.tif") + if file_name[0]: + if self.img_stack.ndim == 3: + tf.imsave(str(file_name[0]), np.float32(self.img_stack.transpose(0, 2, 1))) + elif self.img_stack.ndim == 2: + tf.imsave(str(file_name[0]), np.float32(self.img_stack.T)) + + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + +class ComponentViewer(QtWidgets.QMainWindow): + def __init__(self, comp_stack, energy, comp_spectra, decon_spectra, decomp_map): + super(ComponentViewer, self).__init__() + + # Load the UI Page + uic.loadUi(os.path.join(ui_path, "uis/ComponentView.ui"), self) + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + self.user_wd = os.path.abspath("~") + + self.comp_stack = comp_stack + self.energy = energy + self.comp_spectra = comp_spectra + self.decon_spectra = decon_spectra + self.decomp_map = decomp_map + + (self.dim1, self.dim3, self.dim2) = self.comp_stack.shape + self.hs_comp_number.setMaximum(self.dim1 - 1) + + self.image_view.setImage(self.comp_stack) + self.image_view.setPredefinedGradient("viridis") + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + + self.image_view2.setImage(self.decomp_map) + self.image_view2.setPredefinedGradient("bipolar") + self.image_view2.ui.menuBtn.hide() + self.image_view2.ui.roiBtn.hide() + + # connection + self.update_image() + self.pb_show_all.clicked.connect(self.show_all_spec) + self.hs_comp_number.valueChanged.connect(self.update_image) + self.actionSave.triggered.connect(self.save_comp_data) + self.pb_openScatterPlot.clicked.connect(self.openScatterPlot) + self.pb_showMultiColor.clicked.connect(lambda: self.generateMultiColorView(withSpectra=False)) + self.pb_showMultiImageXANESView.clicked.connect(lambda: self.generateMultiColorView(withSpectra=True)) + + def update_image(self): + im_index = self.hs_comp_number.value() + self.spectrum_view.setLabel("bottom", "Energy") + self.spectrum_view.setLabel("left", "Intensity", "A.U.") + self.spectrum_view.plot(self.energy, self.decon_spectra[:, im_index], clear=True) + self.component_view.setLabel("bottom", "Energy") + self.component_view.setLabel("left", "Weight", "A.U.") + self.component_view.plot(self.energy, self.comp_spectra[:, im_index], clear=True) + self.label_comp_number.setText(f"{im_index + 1}/{self.dim1}") + # self.image_view.setCurrentIndex(im_index-1) + self.image_view.setImage(self.comp_stack[im_index]) + + def openScatterPlot(self): + self.scatter_window = ComponentScatterPlot(self.comp_stack, self.comp_spectra) + + # ph = self.geometry().height() + # pw = self.geometry().width() + # px = self.geometry().x() + # py = self.geometry().y() + # dw = self.scatter_window.width() + # dh = self.scatter_window.height() + # self.scatter_window.setGeometry(px+0.65*pw, py + ph - 2*dh-5, dw, dh) + self.scatter_window.show() + + def show_all_spec(self): + self.spectrum_view.clear() + self.plt_colors = ["g", "b", "r", "c", "m", "y", "w"] * 10 + offsets = np.arange(0, 2, 0.2) + self.spectrum_view.addLegend() + for ii in range(self.decon_spectra.shape[1]): + self.spectrum_view.plot( + self.energy, + (self.decon_spectra[:, ii] / self.decon_spectra[:, ii].max()) + offsets[ii], + pen=self.plt_colors[ii], + name="component" + str(ii + 1), + ) + + def save_comp_data(self): + file_name = QFileDialog().getSaveFileName(self, "save all data", self.user_wd, "data(*tiff *tif *txt *png )") + if file_name[0]: + tf.imsave( + str(file_name[0]) + "_components.tiff", np.float32(self.comp_stack.transpose(0, 2, 1)), imagej=True + ) + tf.imsave(str(file_name[0]) + "_component_masks.tiff", np.float32(self.decomp_map.T), imagej=True) + np.savetxt(str(file_name[0]) + "_deconv_spec.txt", self.decon_spectra) + np.savetxt(str(file_name[0]) + "_component_spec.txt", self.comp_spectra) + + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def generateMultiColorView(self, withSpectra=False): + self.multichanneldict = {} + + for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.comp_stack.transpose(0, 1, 2))): + low, high = np.min(image), np.max(image) + self.multichanneldict[f'Image {n + 1}'] = {'ImageName': f'Image {n + 1}', + 'ImageDir': '.', + 'Image': image, + 'Color': colorName, + 'CmapLimits': (low, high), + 'Opacity': 1.0 + } + + if withSpectra: + compXanesSpetraAll = pd.DataFrame() + compXanesSpetraAll['Energy'] = self.energy + + for n, spec in enumerate(self.decon_spectra.T): + compXanesSpetraAll[f'Component_{n + 1}'] = spec + + self.muli_color_window = MultiXANESWindow(image_dict=self.multichanneldict, + spec_df=compXanesSpetraAll) + else: + self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) + + self.muli_color_window.show() + + # add energy column + + +class ClusterViewer(QtWidgets.QMainWindow): + def __init__(self, decon_images, energy, X_cluster, decon_spectra): + super(ClusterViewer, self).__init__() + + # Load the UI Page + uic.loadUi(os.path.join(ui_path, "uis/ClusterView.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + + self.decon_images = decon_images + self.energy = energy + self.X_cluster = X_cluster + self.decon_spectra = decon_spectra + (self.dim1, self.dim3, self.dim2) = self.decon_images.shape + self.hsb_cluster_number.setMaximum(self.dim1 - 1) + self.X_cluster = X_cluster + + self.image_view.setImage(self.decon_images, autoHistogramRange=True, autoLevels=True) + self.image_view.setPredefinedGradient("viridis") + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + + self.cluster_view.setImage(self.X_cluster, autoHistogramRange=True, autoLevels=True) + self.cluster_view.setPredefinedGradient("bipolar") + self.cluster_view.ui.histogram.hide() + self.cluster_view.ui.menuBtn.hide() + self.cluster_view.ui.roiBtn.hide() + + # connection + self.update_display() + self.hsb_cluster_number.valueChanged.connect(self.update_display) + self.actionSave.triggered.connect(self.save_clust_data) + self.pb_show_all_spec.clicked.connect(self.showAllSpec) + self.pb_showMultiColor.clicked.connect(self.generateMultiColorView) + + def update_display(self): + im_index = self.hsb_cluster_number.value() + self.component_view.setLabel("bottom", "Energy") + self.component_view.setLabel("left", "Intensity", "A.U.") + self.component_view.plot(self.energy, self.decon_spectra[:, im_index], clear=True) + # self.image_view.setCurrentIndex(im_index-1) + self.image_view.setImage(self.decon_images[im_index]) + self.label_comp_number.setText(f"{im_index + 1}/{self.dim1}") + + def save_clust_data(self): + file_name = QFileDialog().getSaveFileName(self, "", "", "data(*tiff *tif *txt *png )") + if file_name[0]: + + tf.imsave( + str(file_name[0]) + "_cluster.tiff", np.float32(self.decon_images.transpose(0, 2, 1)), imagej=True + ) + tf.imsave(str(file_name[0]) + "_cluster_map.tiff", np.float32(self.X_cluster.T), imagej=True) + np.savetxt(str(file_name[0]) + "_deconv_spec.txt", self.decon_spectra) + + else: + logger.error("Saving Cancelled") + self.statusbar.showMessage("Saving Cancelled") + pass + + def showAllSpec(self): + self.component_view.clear() + self.plt_colors = ["g", "b", "r", "c", "m", "y", "w"] * 10 + offsets = np.arange(0, 2, 0.2) + self.component_view.addLegend() + for ii in range(self.decon_spectra.shape[1]): + self.component_view.plot( + self.energy, + (self.decon_spectra[:, ii] / self.decon_spectra[:, ii].max()) + offsets[ii], + pen=self.plt_colors[ii], + name="cluster" + str(ii + 1), + ) + + def generateMultiColorView(self): + self.multichanneldict = {} + + for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.decon_images.transpose(0, 1, 2))): + low, high = np.min(image), np.max(image) + self.multichanneldict[f"Image {n + 1}"] = { + "ImageName": f"Image {n + 1}", + "ImageDir": ".", + "Image": image, + "Color": colorName, + "CmapLimits": (low, high), + "Opacity": 1.0, + } + self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) + self.muli_color_window.show() + + +class XANESViewer(QtWidgets.QMainWindow): + def __init__(self, im_stack=None, e_list=None, refs=None, ref_names=None): + super(XANESViewer, self).__init__() + + uic.loadUi(os.path.join(ui_path, "uis/XANESViewer.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + + self.im_stack = im_stack + self.e_list = e_list + self.refs = refs + self.ref_names = ref_names + self.selected = self.ref_names + self.fitResultDict = {} + self.fit_method = self.cb_xanes_fit_model.currentText() + self.alphaForLM = self.dsb_alphaForLM.value() + + self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( + self.im_stack, self.e_list, self.refs, method=self.fit_method, alphaForLM=self.alphaForLM + ) + + (self.dim1, self.dim2, self.dim3) = self.im_stack.shape + self.cn = int(self.dim2 // 2) + self.sz = np.max([int(self.dim2 * 0.15), int(self.dim3 * 0.15)]) + self.image_roi = pg.RectROI( + [int(self.dim3 // 2), int(self.dim2 // 2)], + [self.sz, self.sz], + pen="w", + maxBounds=QtCore.QRectF(0, 0, self.dim3, self.dim2), + ) + + self.image_roi.addTranslateHandle([0, 0], [2, 2]) + self.image_roi.addRotateHandle([0, 1], [2, 2]) + + # self.image_roi = pg.PolyLineROI([[0, 0], [0, self.sz], [self.sz, self.sz], [self.sz, 0]], + # pos=(int(self.dim2 // 2), int(self.dim3 // 2)), + # maxBounds=QtCore.QRect(0, 0, self.dim3, self.dim2), closed=True) + # self.image_roi.addTranslateHandle([self.sz // 2, self.sz // 2], [2, 2]) + + self.stack_center = int(self.dim1 // 2) + self.stack_width = int(self.dim1 * 0.05) + # self.image_view.setCurrentIndex(self.stack_center) + + self.image_view.addItem(self.image_roi) + self.xdata = self.e_list + self.sb_e_shift.value() + + self.scrollBar_setup() + self.display_image_data() + self.display_references() + self.update_spectrum() + + # connections + self.sb_e_shift.valueChanged.connect(self.update_spectrum) + self.pb_re_fit.clicked.connect(self.re_fit_xanes) + self.pb_edit_refs.clicked.connect(self.choose_refs) + self.image_roi.sigRegionChanged.connect(self.update_spectrum) + self.hsb_xanes_stk.valueChanged.connect(self.display_image_data) + self.hsb_chem_map.valueChanged.connect(self.display_image_data) + # self.pb_showMultiColor.clicked.connect(self.generateMultiColorView) + # self.pb_showCompSpec.clicked.connect(self.showComponentXANES) + self.pb_showCompSpec.clicked.connect(self.generateCompoisteImageSpectrumView) + + # menu + self.actionSave_Chem_Map.triggered.connect(self.save_chem_map) + self.actionSave_R_factor_Image.triggered.connect(self.save_rfactor_img) + #self.actionSave_Live_Fit_Data.triggered.connect(self.pg_export_spec_fit) + self.actionSave_Live_Fit_Data.triggered.connect(self.export_live_data) + self.actionExport_Fit_Stats.triggered.connect(self.exportFitResults) + self.actionExport_Ref_Plot.triggered.connect(self.pg_export_references) + + def scrollBar_setup(self): + self.hsb_xanes_stk.setValue(self.stack_center) + self.hsb_xanes_stk.setMaximum(self.dim1 - 1) + self.hsb_chem_map.setValue(0) + self.hsb_chem_map.setMaximum(self.decon_ims.shape[-1] - 1) + + def display_image_data(self): + + self.image_view.setImage(self.im_stack[self.hsb_xanes_stk.value()]) + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + self.image_view.setPredefinedGradient("viridis") + + self.image_view_maps.setImage(self.decon_ims.transpose(2, 0, 1)[self.hsb_chem_map.value()]) + self.image_view_maps.setPredefinedGradient("bipolar") + self.image_view_maps.ui.menuBtn.hide() + self.image_view_maps.ui.roiBtn.hide() + + def display_references(self): + + self.inter_ref = interploate_E(self.refs, self.xdata) + self.plt_colors = ["c", "m", "y", "w"] * 10 + self.spectrum_view_refs.addLegend() + for ii in range(self.inter_ref.shape[0]): + if len(self.selected) != 0: + self.spectrum_view_refs.plot( + self.xdata, + self.inter_ref[ii], + pen=pg.mkPen(self.plt_colors[ii], width=2), + name=self.selected[1:][ii], + ) + else: + self.spectrum_view_refs.plot( + self.xdata, + self.inter_ref[ii], + pen=pg.mkPen(self.plt_colors[ii], width=2), + name="ref" + str(ii + 1), + ) + + def choose_refs(self): + "Interactively exclude some standards from the reference file" + self.ref_edit_window = RefChooser( + self.ref_names, + self.im_stack, + self.e_list, + self.refs, + self.sb_e_shift.value(), + self.cb_xanes_fit_model.currentText(), + ) + self.ref_edit_window.show() + # self.rf_plot = pg.plot(title="RFactor Tracker") + + # connections + self.ref_edit_window.choosenRefsSignal.connect(self.update_refs) + self.ref_edit_window.fitResultsSignal.connect(self.plotFitResults) + + def update_refs(self, list_): + self.selected = list_ # list_ is the signal from ref chooser + self.update_spectrum() + self.re_fit_xanes() + + def update_spectrum(self): + + self.roi_img = self.image_roi.getArrayRegion(self.im_stack, self.image_view.imageItem, axes=(1, 2)) + sizex, sizey = self.roi_img.shape[1], self.roi_img.shape[2] + posx, posy = self.image_roi.pos() + self.roi_info.setText(f"ROI_Pos: {int(posx)},{int(posy)} ROI_Size: {sizex},{sizey}") + + self.xdata1 = self.e_list + self.sb_e_shift.value() + self.ydata1 = get_mean_spectra(self.roi_img) + self.fit_method = self.cb_xanes_fit_model.currentText() + + if len(self.selected) != 0: + + self.inter_ref = interploate_E(self.refs[self.selected], self.xdata1) + stats, coeffs = xanes_fitting_1D( + self.ydata1, + self.xdata1, + self.refs[self.selected], + method=self.fit_method, + alphaForLM=self.alphaForLM, + ) + + else: + self.inter_ref = interploate_E(self.refs, self.xdata1) + stats, coeffs = xanes_fitting_1D( + self.ydata1, self.xdata1, self.refs, method=self.fit_method, alphaForLM=self.alphaForLM + ) + + self.fit_ = np.dot(coeffs, self.inter_ref) + pen = pg.mkPen("g", width=1.5) + pen2 = pg.mkPen("r", width=1.5) + # pen3 = pg.mkPen("y", width=1.5) + self.spectrum_view.addLegend() + self.spectrum_view.setLabel("bottom", "Energy") + self.spectrum_view.setLabel("left", "Intensity", "A.U.") + self.spectrum_view.plot(self.xdata1, self.ydata1, pen=pen, name="Data", clear=True) + self.spectrum_view.plot(self.xdata1, self.fit_, name="Fit", pen=pen2) + + for n, (coff, ref, plt_clr) in enumerate(zip(coeffs, self.inter_ref, self.plt_colors)): + + if len(self.selected) != 0: + + self.spectrum_view.plot(self.xdata1, np.dot(coff, ref), name=self.selected[1:][n], pen=plt_clr) + else: + self.spectrum_view.plot(self.xdata1, np.dot(coff, ref), name="ref" + str(n + 1), pen=plt_clr) + # set the rfactor value to the line edit slot + self.results = ( + f"Coefficients: {coeffs} \n" + f"R-Factor: {stats['R_Factor']}, R-Square: {stats['R_Square']},\n " + f"Chi-Square: {stats['Chi_Square']}, " + f"Reduced Chi-Square: {stats['Reduced Chi_Square']}" + ) + + self.fit_results.setText(self.results) + + def re_fit_xanes(self): + if len(self.selected) != 0: + self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( + self.im_stack, + self.e_list + self.sb_e_shift.value(), + self.refs[self.selected], + method=self.cb_xanes_fit_model.currentText(), + alphaForLM=self.alphaForLM, + ) + else: + # if non athena file with no header is loaded no ref file cannot be edited + self.decon_ims, self.rfactor, self.coeffs_arr = xanes_fitting( + self.im_stack, + self.e_list + self.sb_e_shift.value(), + self.refs, + method=self.cb_xanes_fit_model.currentText(), + alphaForLM=self.alphaForLM, + ) + + # rfactor is a list of all spectra so take the mean + self.rfactor_mean = np.mean(self.rfactor) + self.image_view_maps.setImage(self.decon_ims.transpose(2, 0, 1)) + self.scrollBar_setup() + + def plotFitResults(self, decon_ims, rfactor_mean, coeff_array): + # upadte the chem maps and scrollbar params + self.image_view_maps.setImage(decon_ims.transpose(2, 0, 1)) + # self.hsb_chem_map.setValue(0) + # self.hsb_chem_map.setMaximum(decon_ims.shape[-1]-1) + + # set the rfactor value to the line edit slot + self.le_r_sq.setText(f"{rfactor_mean :.4f}") + + def showComponentXANES(self): + compNum = self.hsb_chem_map.value() + currentComp = self.decon_ims.transpose(2, 0, 1)[compNum] + currentCompMask = currentComp > 0 + yData = applyMaskGetMeanSpectrum(self.im_stack, currentCompMask) + xanes_comp_plot = pg.plot( + self.e_list + self.sb_e_shift.value(), + yData, + title=f"Component_{compNum}", + pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), + symbol="o", + ) + xanes_comp_plot.setLabel("bottom", "Energy (keV)") + xanes_comp_plot.setLabel("left", "Intensity") + + def plotDeconvSpectrum(self, clusterSigma=0): + + try: + self.ref_plot.close() + + except: + pass + + self.ref_plot = plot(title="Deconvoluted XANES Spectra") + self.ref_plot.setLabel("bottom", "Energy") + self.ref_plot.setLabel("left", "Intensity") + self.ref_plot.addLegend() + + for n, compImage in enumerate(self.decon_ims.transpose(2, 0, 1)): + mask = np.where(compImage > clusterSigma * np.std(compImage), compImage, 0) + + self.ref_plot.plot( + self.xdata1, + get_mean_spectra(self.im_stack * mask), + pen=pg.mkPen(self.plt_colors[n], width=2), + name=f'Component_{n + 1}' + ) + + def generateCompoisteImageSpectrumView(self): + self.multichanneldict = {} + + spectrumDF = getDeconvolutedXANESSpectrum(self.im_stack, self.decon_ims.transpose(2, 0, 1), + self.xdata1, clusterSigma=3) + + for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.decon_ims.transpose((2, 0, 1)))): + low, high = np.min(image), np.max(image) + self.multichanneldict[f'Image {n + 1}'] = {'ImageName': f'Image {n + 1}', + 'ImageDir': '.', + 'Image': image, + 'Color': colorName, + 'CmapLimits': (low, high), + 'Opacity': 1.0 + } + self.muli_color_window = MultiXANESWindow(image_dict=self.multichanneldict, spec_df=spectrumDF) + self.muli_color_window.show() + + def generateMultiColorView(self): + self.multichanneldict = {} + + for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), self.decon_ims.transpose((2, 0, 1)))): + low, high = np.min(image), np.max(image) + self.multichanneldict[f"Image {n + 1}"] = { + "ImageName": f"Image {n + 1}", + "ImageDir": ".", + "Image": image, + "Color": colorName, + "CmapLimits": (low, high), + "Opacity": 1.0, + } + self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) + self.muli_color_window.show() + + def save_chem_map(self): + file_name = QFileDialog().getSaveFileName(self, + "save image", + os.path.join(self.user_wd,"chemical_map.tiff"), + "image data (*tiff)") + if file_name[0]: + tf.imsave(str(file_name[0]), np.float32(self.decon_ims.transpose(2, 0, 1)), imagej=True) + self.user_wd = os.path.dirname(file_name[0]) + else: + logger.error("No file to save") + pass + + def save_rfactor_img(self): + file_name = QFileDialog().getSaveFileName(self, + "save image", + os.path.join(self.user_wd,"r-factor_map.tiff"), + "image data (*tiff)") + if file_name[0]: + tf.imsave(str(file_name[0]), np.float32(self.rfactor), imagej=True) + self.user_wd = os.path.dirname(file_name[0]) + else: + logger.error("No file to save") + pass + + def save_spec_fit(self): + try: + to_save = np.column_stack([self.xdata1, self.ydata1, self.fit_]) + file_name = QFileDialog().getSaveFileName(self, + "save spectrum", + os.path.join(self.user_wd,"spec_fit.txt"), + "spectrum and fit (*txt)") + if file_name[0]: + np.savetxt(str(file_name[0]) + ".txt", to_save) + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + except Exception: + logger.error("No file to save") + pass + + def pg_export_spec_fit(self): + + exporter = pg.exporters.CSVExporter(self.spectrum_view.plotItem) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, + "save spectrum", + os.path.join(self.user_wd,"spec_fit.txt"), + "spectrum and fit (*csv)") + if file_name[0]: + exporter.export(str(file_name[0]) + ".csv") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def pg_export_references(self): + + exporter = pg.exporters.CSVExporter(self.spectrum_view_refs.plotItem) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, + "save references", + os.path.join(self.user_wd,"xanes_references.csv"), + "column data (*csv)" + ) + if file_name[0]: + exporter.export(str(file_name[0])) + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def exportFitResults(self): + file_name = QFileDialog().getSaveFileName(self, + "save txt", + os.path.join(self.user_wd,"xanes_1D_fit_results.txt"), + "txt data (*txt)") + if file_name[0]: + with open(file_name[0], "w") as file: + file.write(self.results) + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def export_live_data(self): + file_name = QFileDialog().getSaveFileName(self, + "save all live data", + os.path.join(self.user_wd,"xanes_fit"), + "All Files (*)") + exporter_csv = pg.exporters.CSVExporter(self.spectrum_view.plotItem) + exporter_csv.parameters()["columnMode"] = "(x,y,y,y) for all plots" + exporter_png = pg.exporters.ImageExporter(self.spectrum_view.getViewBox()) + exporter_img_png = pg.exporters.ImageExporter(self.image_view.getView()) + if file_name[0]: + exporter_csv.export(str(file_name[0]+"_data.csv")) + exporter_png.export(str(file_name[0]+"_spec_image.png")) + exporter_img_png.export(str(file_name[0]+"_area_map.png")) + with open(file_name[0]+"_params.txt", "w") as file: + file.write(self.results) + self.user_wd = os.path.dirname(file_name[0]) + + +class RefChooser(QtWidgets.QMainWindow): + choosenRefsSignal: pyqtSignal = QtCore.pyqtSignal(list) + fitResultsSignal: pyqtSignal = QtCore.pyqtSignal(np.ndarray, float, np.ndarray) + + def __init__(self, ref_names, im_stack, e_list, refs, e_shift, fit_model): + super(RefChooser, self).__init__() + uic.loadUi(os.path.join(ui_path, "uis/RefChooser.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + self.ref_names = ref_names + self.refs = refs + self.im_stack = im_stack + self.e_list = e_list + self.e_shift = e_shift + self.fit_model = fit_model + + self.all_boxes = [] + self.rFactorList = [] + + self.displayCombinations() + + # selection become more apparent than default with red-ish color + self.tableWidget.setStyleSheet("background-color: white; selection-background-color: rgb(200,0,0);") + + # add a line to the plot to walk through the table. Note that the table is not sorted + self.selectionLine = pg.InfiniteLine( + pos=1, angle=90, pen=pg.mkPen("m", width=2.5), movable=True, bounds=None, label="Move Me!" + ) + self.stat_view.setLabel("bottom", "Fit ID") + self.stat_view.setLabel("left", "Reduced Chi^2") + + for n, i in enumerate(self.ref_names): + self.cb_i = QtWidgets.QCheckBox(self.ref_box_frame) + if n == 0: + self.cb_i.setChecked(True) + self.cb_i.setEnabled(False) + self.cb_i.setObjectName(i) + self.cb_i.setText(i) + self.gridLayout_2.addWidget(self.cb_i, n, 0, 1, 1) + self.cb_i.toggled.connect(self.enableApply) + self.all_boxes.append(self.cb_i) + + # connections + self.pb_apply.clicked.connect(self.clickedWhichAre) + self.pb_combo.clicked.connect(self.tryAllCombo) + self.actionExport_Results_csv.triggered.connect(self.exportFitResults) + self.selectionLine.sigPositionChanged.connect(self.updateFitWithLine) + self.tableWidget.itemSelectionChanged.connect(self.updateWithTableSelection) + # self.stat_view.scene().sigMouseClicked.connect(self.moveSelectionLine) + self.stat_view.mouseDoubleClickEvent = self.moveSelectionLine + self.sb_max_combo.valueChanged.connect(self.displayCombinations) + # self.pb_sort_with_r.clicked.connect(lambda: self.tableWidget.sortItems(3, QtCore.Qt.AscendingOrder)) + self.pb_sort_with_r.clicked.connect(self.sortTable) + self.cb_sorter.currentTextChanged.connect(self.sortTable) + + def clickedWhich(self): + button_name = self.sender() + + def populateChecked(self): + self.onlyCheckedBoxes = [] + for names in self.all_boxes: + if names.isChecked(): + self.onlyCheckedBoxes.append(names.objectName()) + + QtCore.pyqtSlot() + + def clickedWhichAre(self): + self.populateChecked() + self.choosenRefsSignal.emit(self.onlyCheckedBoxes) + + def generateRefList(self, ref_list, maxCombo, minCombo=1): + + """ + Creates a list of reference combinations for xanes fitting + + Paramaters; + + ref_list (list): list of ref names from the header + maxCombo (int): maximum number of ref lists in combination + minCombo (int): min number of ref lists in combination + + returns; + + 1. int: length of total number of combinations + 2. list: all the combinations + + """ + + if not maxCombo > len(ref_list): + + iter_list = [] + while minCombo < maxCombo + 1: + iter_list += list(combinations(ref_list, minCombo)) + minCombo += 1 + return len(iter_list), iter_list + + else: + raise ValueError(" Maximum numbinations cannot be larger than number of list items") + + def displayCombinations(self): + niter, self.iter_list = self.generateRefList(self.ref_names[1:], self.sb_max_combo.value()) + self.label_nComb.setText(str(niter) + " Combinations") + + @QtCore.pyqtSlot() + def tryAllCombo(self): + # empty list to to keep track and plot of reduced chi2 of all the fits + self.rfactor_list = [] + + # create dataframe for the table + self.df = pd.DataFrame( + columns=["Fit Number", "References", "Coefficients", "R-Factor", "R^2", "chi^2", "red-chi^2", "Score"] + ) + + # df columns is the header for the table widget + self.tableWidget.setHorizontalHeaderLabels(self.df.columns) + # self.iter_list = list(combinations(self.ref_names[1:],self.sb_max_combo.value())) + + niter, self.iter_list = self.generateRefList(self.ref_names[1:], self.sb_max_combo.value()) + tot_combo = len(self.iter_list) + for n, refs in enumerate(self.iter_list): + self.statusbar.showMessage(f"{n + 1}/{tot_combo}") + selectedRefs = list((str(self.ref_names[0]),) + refs) + self.fit_combo_progress.setValue((n + 1) * 100 / tot_combo) + self.stat, self.coeffs_arr = xanes_fitting_Binned( + self.im_stack, self.e_list + self.e_shift, self.refs[selectedRefs], method=self.fit_model + ) + + self.rfactor_list.append(self.stat["Reduced Chi_Square"]) + self.stat_view.plot( + x=np.arange(n + 1), + y=self.rfactor_list, + clear=True, + title="Reduced Chi^2", + pen=pg.mkPen("y", width=2, style=QtCore.Qt.DotLine), + symbol="o", + ) + + # arbitary number to rank the best fit + fit_score = (self.stat["R_Square"] + np.sum(self.coeffs_arr)) / ( + self.stat["R_Factor"] + self.stat["Reduced Chi_Square"] + ) + + resultsDict = { + "Fit Number": n, + "References": str(selectedRefs[1:]), + "Coefficients": str(np.around(self.coeffs_arr, 4)), + "Sum of Coefficients": str(np.around(np.sum(self.coeffs_arr), 4)), + "R-Factor": self.stat["R_Factor"], + "R^2": self.stat["R_Square"], + "chi^2": self.stat["Chi_Square"], + "red-chi^2": self.stat["Reduced Chi_Square"], + "Score": np.around(fit_score, 4), + } + + self.df = pd.concat([self.df, pd.DataFrame([resultsDict])], ignore_index=True) + + self.dataFrametoQTable(self.df) + QtTest.QTest.qWait(0.1) # hepls with real time plotting + + self.stat_view.addItem(self.selectionLine) + + def dataFrametoQTable(self, df_: pd.DataFrame): + nRows = len(df_.index) + nColumns = len(df_.columns) + self.tableWidget.setRowCount(nRows) + self.tableWidget.setColumnCount(nColumns) + self.tableWidget.setHorizontalHeaderLabels(df_.columns) + + for i in range(nRows): + for j in range(nColumns): + cell = QtWidgets.QTableWidgetItem(str(df_.values[i][j])) + self.tableWidget.setItem(i, j, cell) + + # set the property of the table view. Size policy to make the contents justified + self.tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.tableWidget.resizeColumnsToContents() + + def exportFitResults(self): + file_name = QFileDialog().getSaveFileName(self, "save csv", "xanes_fit_results_log.csv", "txt data (*csv)") + if file_name[0]: + with open(str(file_name[0]), "w") as fp: + self.df.to_csv(fp) + else: + pass + + def selectTableAndCheckBox(self, x): + nSelection = int(round(x)) + self.tableWidget.selectRow(nSelection) + fit_num = int(self.tableWidget.item(nSelection, 0).text()) + refs_selected = self.iter_list[fit_num] + + # reset all the checkboxes to uncheck state, except the energy + for checkstate in self.findChildren(QtWidgets.QCheckBox): + if checkstate.isEnabled(): + checkstate.setChecked(False) + + for cb_names in refs_selected: + checkbox = self.findChild(QtWidgets.QCheckBox, name=cb_names) + checkbox.setChecked(True) + + def updateFitWithLine(self): + pos_x, pos_y = self.selectionLine.pos() + x = self.df.index[self.df[str("Fit Number")] == np.round(pos_x)][0] + self.selectTableAndCheckBox(x) + + def updateWithTableSelection(self): + x = self.tableWidget.currentRow() + self.selectTableAndCheckBox(x) + + def moveSelectionLine(self, event): + if event.button() == QtCore.Qt.LeftButton: + Pos = self.stat_view.plotItem.vb.mapSceneToView(event.pos()) + self.selectionLine.setPos(Pos.x()) + + def sortTable(self): + sorter_dict = { + "R-Factor": "R-Factor", + "R-Square": "R^2", + "Chi-Square": "chi^2", + "Reduced Chi-Square": "red-chi^2", + "Fit Number": "Fit Number", + } + sorter = sorter_dict[self.cb_sorter.currentText()] + self.df = self.df.sort_values(sorter, ignore_index=True) + self.dataFrametoQTable(self.df) + + def enableApply(self): + + """ """ + self.populateChecked() + if len(self.onlyCheckedBoxes) > 1: + self.pb_apply.setEnabled(True) + else: + self.pb_apply.setEnabled(False) + +class ScatterPlot(QtWidgets.QMainWindow): + def __init__(self, img1, img2, nameTuple): + super(ScatterPlot, self).__init__() + + uic.loadUi(os.path.join(ui_path, "uis/ScatterView.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + self.clearPgPlot() + self.w1 = self.scatterViewer.addPlot() + self.img1 = img1 + self.img2 = img2 + self.nameTuple = nameTuple + x, y = np.shape(self.img1) + self.s1 = pg.ScatterPlotItem(size=2, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 0, 255)) + # print(self.s1) + + # create three polyline ROIs for masking + Xsize = self.img1.max() / 6 + Ysize = self.img2.max() / 6 + + self.scatter_mask = pg.PolyLineROI( + [[0, 0], [0, Ysize], [Xsize / 2, Ysize * 1.5], [Xsize, Ysize], [Xsize, 0]], + pos=None, + pen=pg.mkPen("r", width=2), + hoverPen=pg.mkPen("w", width=2), + closed=True, + removable=True, + ) + + self.scatter_mask2 = pg.PolyLineROI( + [ + [Xsize * 1.2, 0], + [Xsize * 1.2, Ysize * 2], + [Xsize * 2, Ysize * 2], + [Xsize * 3, Ysize], + [Xsize * 2, 0], + ], + pos=None, + pen=pg.mkPen("g", width=2), + hoverPen=pg.mkPen("w", width=2), + closed=True, + removable=True, + ) + self.scatter_mask3 = pg.PolyLineROI( + [ + [Xsize * 2.5, 0], + [Xsize * 2.5, Ysize], + [Xsize * 4, Ysize], + [Xsize * 4, 0], + [Xsize * 3.7, Ysize * -0.5], + ], + pos=None, + pen=pg.mkPen("c", width=2), + hoverPen=pg.mkPen("w", width=2), + closed=True, + removable=True, + ) + + self.fitScatter = self.fitScatter2 = self.fitScatter3 = None + + self.rois = { + "ROI 1": (self.scatter_mask, self.rb_roi1.isChecked(), self.fitScatter), + "ROI 2": (self.scatter_mask2, self.rb_roi2.isChecked(), self.fitScatter2), + "ROI 3": (self.scatter_mask3, self.rb_roi3.isChecked(), self.fitScatter3), + } + + self.windowNames = {"ROI 1": self.fitScatter, "ROI 2": self.fitScatter2, "ROI 3": self.fitScatter3} + + self.s1.setData(self.img1.flatten(), self.img2.flatten()) + self.w1.setLabel("bottom", self.nameTuple[0], "counts") + self.label_img1.setText(self.nameTuple[0]) + self.w1.setLabel("left", self.nameTuple[1], "counts") + self.label_img2.setText(self.nameTuple[1]) + self.w1.addItem(self.s1) + + self.image_view.setImage(self.img1) + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + self.image_view.setPredefinedGradient("thermal") + + self.image_view2.setImage(self.img2) + self.image_view2.ui.menuBtn.hide() + self.image_view2.ui.roiBtn.hide() + self.image_view2.setPredefinedGradient("thermal") + + # connections + self.actionSave_Plot.triggered.connect(self.pg_export_correlation) + self.actionSave_Images.triggered.connect(self.tiff_export_images) + # self.pb_define_mask.clicked.connect(lambda:self.createMask(self.scatter_mask)) + self.pb_define_mask.clicked.connect(self.addMultipleROIs) + # self.pb_apply_mask.clicked.connect(lambda:self.getMaskRegion(self.scatter_mask)) + self.pb_apply_mask.clicked.connect(self.applyMultipleROIs) + self.pb_clear_mask.clicked.connect(self.clearMultipleROIs) + self.pb_compositeScatter.clicked.connect(self.createCompositeScatter) + [rbs.clicked.connect(self.updateROIDict) for rbs in [self.rb_roi1, self.rb_roi2, self.rb_roi3]] + + def pg_export_correlation(self): + + exporter = pg.exporters.CSVExporter(self.w1) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, + "save correlation", + os.path.join(self.user_wd,"correlation.csv"), + "spectrum and fit (*csv)") + if file_name[0]: + exporter.export(str(file_name[0]) + ".csv") + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def tiff_export_images(self): + file_name = QFileDialog().getSaveFileName(self, + "save images", + os.path.join(self.user_wd,"image.txt"), + "spectrum and fit (*tiff)") + if file_name[0]: + tf.imsave(str(file_name[0]) + ".tiff", np.dstack([self.img1, self.img2]).T) + self.statusbar.showMessage(f"Images saved to {str(file_name[0])}") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def createMask(self, ROIName): + + try: + self.w1.removeItem(ROIName) + except Exception: + pass + self.w1.addItem(ROIName) + + def clearMask(self, ROIName): + self.w1.removeItem(ROIName) + + def clearPgPlot(self): + try: + self.masked_img.close() + except Exception: + pass + + def getMaskRegion(self, ROIName, generateSeperateWindows=True): + + """filter scatterplot points using polylineROI region""" + + # Ref : https://stackoverflow.com/questions/57719303/how-to-map-mouse-position-on-a-scatterplot + + # get the roi region:QPaintPathObject + roiShape = self.rois[ROIName][0].mapToItem(self.s1, self.rois[ROIName][0].shape()) + + # get data in the scatter plot + scatterData = np.array(self.s1.getData()) + + # generate a binary mask for points inside or outside the roishape + selected = [roiShape.contains(QtCore.QPointF(pt[0], pt[1])) for pt in scatterData.T] + + # reshape the mask to image dimensions + self.mask2D = np.reshape(selected, (self.img1.shape)) + + # get masked image1 + self.maskedImage = self.mask2D * self.img1 + + # get rid of the (0,0) values in the masked array + self.xData, self.yData = np.compress(selected, scatterData[0]), np.compress(selected, scatterData[1]) + + # linear regeression of the filtered X,Y data + result = linregress(self.xData, self.yData) + + # Pearson's correlation of the filtered X,Y data + pr, pp = stats.pearsonr(self.xData, self.yData) + + # apply the solved equation to xData to generate the fit line + self.yyData = result.intercept + result.slope * self.xData + + # Prepare strings for fit results and stats + self.fitLineEqn = ( + f" y = x*{result.slope :.3e} + {result.intercept :.3e}," + f"\n R^2 = {result.rvalue**2 :.3f}, r = {pr :.3f}" + ) + FitStats1 = f" Slope Error = {result.stderr :.3e}, Intercept Error = {result.intercept_stderr :.3e}\n" + FitStats2 = f" Pearson’s correlation coefficient = {pr :.3f}" + refs = "\n\n ***References****\n\n scipy.stats.linregress, scipy.stats.pearsonr " + fitStats = ( + f"\n ***{ROIName} Fit Results***\n\n" + " Equation: " + self.fitLineEqn + FitStats1 + FitStats2 + refs + ) + + # generate new window to plot the results + + if generateSeperateWindows: + self.windowNames[ROIName] = MaskedScatterPlotFit( + [self.xData, self.yData], + [self.xData, self.yyData], + self.mask2D, + self.maskedImage, + fitStats, + self.fitLineEqn, + self.nameTuple, + ) + self.windowNames[ROIName].show() + + """ + from scipy.linalg import lstsq + M = xData[:, np.newaxis]**[0, 1] #use >1 for polynomial fits + p, res, rnk, s = lstsq(M, yData) + yyData = p[0] + p[1]*xData + """ + + def updateROIDict(self): + self.rois = { + "ROI 1": (self.scatter_mask, self.rb_roi1.isChecked()), + "ROI 2": (self.scatter_mask2, self.rb_roi2.isChecked()), + "ROI 3": (self.scatter_mask3, self.rb_roi3.isChecked()), + } + + def applyMultipleROIs(self): + with pg.BusyCursor(): + self.updateROIDict() + for key in self.rois.keys(): + if self.rois[key][1]: + self.getMaskRegion(key) + else: + pass + + def addMultipleROIs(self): + self.updateROIDict() + for key in self.rois.keys(): + if self.rois[key][1]: + self.createMask(self.rois[key][0]) + else: + self.clearMask(self.rois[key][0]) + + def clearMultipleROIs(self): + self.updateROIDict() + for key in self.rois.keys(): + if not self.rois[key][1]: + self.clearMask(self.rois[key][0]) + else: + pass + + def createCompositeScatter(self): + + points = [] + fitLine = [] + masks = [] + roiFitEqn = {} + + self.updateROIDict() + for n, key in enumerate(self.rois.keys()): + if self.rois[key][1]: + self.getMaskRegion(key, generateSeperateWindows=False) + points.append(np.column_stack([self.xData, self.yData])) + fitLine.append(np.column_stack([self.xData, self.yyData])) + masks.append(self.mask2D) + roiFitEqn[key] = self.fitLineEqn + else: + pass + + self.compositeScatterWindow = CompositeScatterPlot( + points, + fitLine, + np.array(masks), + roiFitEqn, + self.nameTuple + ) + self.compositeScatterWindow.show() + + def _createCompositeScatter(self): + self.scatterColors = ["w", "c", "y", "k", "m"] + points = [] + fitLine = [] + + self.updateROIDict() + for n, key in enumerate(self.rois.keys()): + if self.rois[key][1]: + self.getMaskRegion(key, generateSeperateWindows=False) + + for x, y, yy in zip(self.xData, self.yData, self.yyData): + + points.append( + { + "pos": (x, y), + "data": "id", + "size": 3, + "pen": pg.mkPen(None), + "brush": self.scatterColors[n], + } + ) + fitLine.extend(np.column_stack((self.xData, self.yyData))) + else: + pass + + logger.info(f" fitline shape: {np.shape(fitLine)}") + self.compositeScatterWindow = CompositeScatterPlot(points, np.array(fitLine)) + self.compositeScatterWindow.show() + + def getROIParams(self): + print(np.array(self.scatter_mask.getSceneHandlePositions())) + + +class MaskedScatterPlotFit(QtWidgets.QMainWindow): + def __init__(self, scatterData, fitData, mask, maskedImage, fitString, fitEquation, nameTuple): + super(MaskedScatterPlotFit, self).__init__() + + uic.loadUi(os.path.join(ui_path, "uis/maskedScatterPlotFit.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + self.scatterData = scatterData + self.fitData = fitData + self.mask = mask + self.maskedImage = maskedImage + self.fitString = fitString + self.fitEquation = fitEquation + self.nameTuple = nameTuple + + # set the graphicslayoutwidget in the ui as canvas + self.canvas = self.scatterViewer.addPlot() + self.canvas.addLegend() + self.canvas.setLabel("bottom", self.nameTuple[0], "counts") + self.canvas.setLabel("left", self.nameTuple[1], "counts") + self.gb_maskedImage1.setTitle(f" Masked {self.nameTuple[0]}") + + # generate a scatter plot item + self.scattered = pg.ScatterPlotItem(size=3.5, pen=pg.mkPen(None), brush=pg.mkBrush(5, 214, 255, 200)) + + # set scatter plot data + self.scattered.setData(scatterData[0], scatterData[1], name="Data") + + # set z value negative to show scatter data behind the fit line + self.scattered.setZValue(-10) + + # add scatter plot to the canvas + self.canvas.addItem(self.scattered) + + # generate plotitem for fit line + self.fitLinePlot = pg.PlotDataItem(pen=pg.mkPen(pg.mkColor(220, 20, 60), width=3.3)) + + # set line plot data + self.fitLinePlot.setData(fitData[0], fitData[1], name="Linear Fit") + + # add line plot to the canvas + self.canvas.addItem(self.fitLinePlot) + + # display Mask + self.imageView_mask.setImage(self.mask) + self.imageView_mask.ui.menuBtn.hide() + self.imageView_mask.ui.roiBtn.hide() + self.imageView_mask.setPredefinedGradient("plasma") + + # display masked Image + self.imageView_maskedImage.setImage(self.maskedImage) + self.imageView_maskedImage.ui.menuBtn.hide() + self.imageView_maskedImage.ui.roiBtn.hide() + self.imageView_maskedImage.setPredefinedGradient("viridis") + + # display Fit stats + self.text_fit_results.setPlainText(fitString) + self.canvas.setTitle(self.fitEquation, color="r") + + # connections + self.pb_copy_results.clicked.connect(self.copyFitResults) + self.pb_save_results.clicked.connect(self.saveFitResults) + self.actionSave_Plot.triggered.connect(self.pg_export_correlation) + self.actionSaveMask.triggered.connect(self.saveMask) + self.actionSaveMaskedImage.triggered.connect(self.saveImage) + + def saveFitResults(self): + S__File = QFileDialog.getSaveFileName(self, "save txt", "correlationPlotFit.txt", "txt data (*txt)") + + Text = self.text_fit_results.toPlainText() + if S__File[0]: + with open(S__File[0], "w") as file: + file.write(Text) + + def copyFitResults(self): + self.text_fit_results.selectAll() + self.text_fit_results.copy() + self.statusbar.showMessage("text copied to clipboard") + + def pg_export_correlation(self): + + exporter = pg.exporters.CSVExporter(self.canvas) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName( + self, "save correlation", "scatterData.csv", "spectrum and fit (*csv)" + ) + if file_name[0]: + exporter.export(str(file_name[0])) + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + else: + pass + + def saveImage(self): + + file_name = QFileDialog().getSaveFileName(self, "Save image data", "image.tiff", "image file(*tiff *tif )") + if file_name[0]: + tf.imsave(str(file_name[0]), self.maskedImage) + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + else: + self.statusbar.showMessage("Saving cancelled") + pass + + def saveMask(self): + + file_name = QFileDialog().getSaveFileName(self, "Save image data", "mask.tiff", "image file(*tiff *tif )") + if file_name[0]: + tf.imsave(str(file_name[0]), self.mask) + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + else: + self.statusbar.showMessage("Saving cancelled") + pass + +class ComponentScatterPlot(QtWidgets.QMainWindow): + def __init__(self, decomp_stack, specs): + super(ComponentScatterPlot, self).__init__() + + uic.loadUi(os.path.join(ui_path, "uis/ComponentScatterPlot.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + self.w1 = self.scatterViewer.addPlot() + self.decomp_stack = decomp_stack + self.specs = specs + (self.dim1, self.dim3, self.dim2) = self.decomp_stack.shape + # fill the combonbox depending in the number of components for scatter plot + for n, combs in enumerate(combinations(np.arange(self.dim1), 2)): + self.cb_scatter_comp.addItem(str(combs)) + self.cb_scatter_comp.setItemData(n, combs) + + self.s1 = pg.ScatterPlotItem(size=3, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 0, 120)) + + self.setImageAndScatterPlot() + # connections + self.actionSave_Plot.triggered.connect(self.pg_export_correlation) + self.actionSave_Images.triggered.connect(self.tiff_export_images) + self.pb_updateComponents.clicked.connect(self.setImageAndScatterPlot) + self.pb_define_mask.clicked.connect(self.createMask) + self.pb_apply_mask.clicked.connect(self.getMaskRegion) + self.pb_reset_mask.clicked.connect(self.resetMask) + self.pb_addALine.clicked.connect(lambda: self.createMask(Line=True)) + + def setImageAndScatterPlot(self): + + try: + self.s1.clear() + except Exception: + pass + + comp_tuple = self.cb_scatter_comp.currentData() + self.img1, self.img2 = self.decomp_stack[comp_tuple[0]], self.decomp_stack[comp_tuple[-1]] + self.image_view.setImage(self.decomp_stack[comp_tuple[0]]) + self.image_view.ui.menuBtn.hide() + self.image_view.ui.roiBtn.hide() + self.image_view.setPredefinedGradient("bipolar") + + self.image_view2.setImage(self.decomp_stack[comp_tuple[-1]]) + self.image_view2.ui.menuBtn.hide() + self.image_view2.ui.roiBtn.hide() + self.image_view2.setPredefinedGradient("bipolar") + + points = [] + for i, j in zip(self.img1.flatten(), self.img2.flatten()): + + points.append( + { + "pos": (i, j), + "data": "id", + "size": 5, + "pen": pg.mkPen(None), + "brush": pg.mkBrush(255, 255, 0, 160), + } + ) + + self.s1.addPoints(points) + self.w1.addItem(self.s1) + # self.s1.setData(self.specs[:, comp_tuple[0]], self.specs[:, comp_tuple[-1]]) + self.w1.setLabel("bottom", f"PC{comp_tuple[0]+1}") + self.w1.setLabel("left", f"PC{comp_tuple[-1]+1}") + self.label_im1.setText(f"PC{comp_tuple[0]+1}") + self.label_im2.setText(f"PC{comp_tuple[-1]+1}") + + def createMask(self, Line=False): + + self.size = self.img1.max() / 10 + self.pos = int(self.img1.mean()) + + if Line: + self.lineROI = pg.LineSegmentROI( + [0, 1], + pos=(self.pos, self.pos), + pen=pg.mkPen("r", width=4), + hoverPen=pg.mkPen("g", width=4), + removable=True, + ) + self.w1.addItem(self.lineROI) + + else: + + self.scatter_mask = pg.PolyLineROI( + [[0, 0], [0, self.size], [self.size, self.size], [self.size, 0]], + pos=(self.pos, self.pos), + pen=pg.mkPen("r", width=4), + hoverPen=pg.mkPen("g", width=4), + closed=True, + removable=True, + ) + + self.w1.addItem(self.scatter_mask) + + def resetMask(self): + self.clearMask() + self.createMask() + + def clearMask(self): + try: + self.w1.removeItem(self.scatter_mask) + except AttributeError: + pass + + def clearPgPlot(self): + try: + self.masked_img.close() + except Exception: + pass + + def getMaskRegion(self): + + # Ref : https://stackoverflow.com/questions/57719303/how-to-map-mouse-position-on-a-scatterplot + + roiShape = self.scatter_mask.mapToItem(self.s1, self.scatter_mask.shape()) + self._points = list() + logger.info("Building Scatter Plot Window; Please wait..") + for i in range(len(self.img1.flatten())): + self._points.append(QtCore.QPointF(self.img1.flatten()[i], self.img2.flatten()[i])) + + selected = [roiShape.contains(pt) for pt in self._points] + img_selected = np.reshape(selected, (self.img1.shape)) + + self.masked_img = singleStackViewer(img_selected * self.img1, gradient="bipolar") + self.masked_img.show() + + def pg_export_correlation(self): + + exporter = pg.exporters.CSVExporter(self.w1) + exporter.parameters()["columnMode"] = "(x,y,y,y) for all plots" + file_name = QFileDialog().getSaveFileName(self, "save correlation", "", "spectrum and fit (*csv)") + if file_name[0]: + exporter.export(str(file_name[0]) + ".csv") + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + else: + pass + + def tiff_export_images(self): + file_name = QFileDialog().getSaveFileName(self, "save images", "", "spectrum and fit (*tiff)") + if file_name[0]: + tf.imsave(str(file_name[0]) + ".tiff", np.dstack([self.img1, self.img2]).T) + self.statusbar.showMessage(f"Images saved to {str(file_name[0])}") + else: + pass + +class LoadingScreen(QtWidgets.QSplashScreen): + def __init__(self): + super(LoadingScreen, self).__init__() + uic.loadUi(os.path.join(ui_path, "uis/animationWindow.ui"), self) + self.setWindowOpacity(0.65) + self.movie = QMovie("uis/animation.gif") + self.label.setMovie(self.movie) + + def mousePressEvent(self, event): + # disable default "click-to-dismiss" behaviour + pass + + def startAnimation(self): + self.movie.start() + self.show() + + def stopAnimation(self): + self.movie.stop() + self.hide() + +class CompositeScatterPlot(QtWidgets.QMainWindow): + def __init__(self, scatterPoints, fitLine, maskImages, fitEquations, nameTuple): + super(CompositeScatterPlot, self).__init__() + + uic.loadUi(os.path.join(ui_path, "uis/multipleScatterFit.ui"), self) + self.user_wd = os.path.abspath("~") + self.centralwidget.setStyleSheet(open(os.path.join(ui_path, "css/defaultStyle.css")).read()) + + self.scatterPoints = scatterPoints + # print(f"{np.shape(self.scatterPoints) = }") + self.fitLine = fitLine + #print(f"{np.shape(self.fitLine) = }") + self.scatterColors = ["r", (0, 115, 0), (4, 186, 186), "c", "w", "k"] + self.fitColors = ["b", "r", "m", "k", "b"] + self.roiNames = list(fitEquations.keys()) + self.fitEqns = list(fitEquations.values()) + # print(f"{np.shape(self.roiNames) = }") + # print(f"{np.shape(self.fitEqns) = }") + self.nameTuple = nameTuple + self.maskImages = maskImages + + # self.scatterViewer.setBackground('w') + # set the graphicslayoutwidget in the ui as canvas + self.canvas = self.scatterViewer.addPlot() + self.canvas.addLegend() + self.canvas.setLabel("bottom", self.nameTuple[0], "counts") + self.canvas.setLabel("left", self.nameTuple[1], "counts") + + # connections + self.actionExport.triggered.connect(self.exportData) + self.actionSave_as_PNG.triggered.connect(self.exportAsPNG) + self.actionGenerate_MultiColor_Mask.triggered.connect(self.generateMultiColorView) + self.actionWhite.triggered.connect(lambda: self.scatterViewer.setBackground("w")) + self.actionBlack.triggered.connect(lambda: self.scatterViewer.setBackground("k")) + + with pg.BusyCursor(): + + for arr, fitline, clr, fitClr, rname, feqn in zip( + self.scatterPoints, self.fitLine, self.scatterColors, self.fitColors, self.roiNames, self.fitEqns + ): + + sctrPoints = [] + for pt in arr: + sctrPoints.append( + {"pos": (pt[0], pt[1]), "data": "id", "size": 3, "pen": pg.mkPen(None), "brush": clr} + ) + + # generate a scatter plot item + self.scattered = pg.ScatterPlotItem(size=4.5, pen=clr, brush=pg.mkBrush(5, 214, 255, 200)) + # set scatter plot data + self.scattered.addPoints(sctrPoints, name=rname) + + # set z value negative to show scatter data behind the fit line + self.scattered.setZValue(-10) + + # add scatter plot to the canvas + self.canvas.addItem(self.scattered) + + # generate plotitem for fit line + self.fitLinePlot = pg.PlotDataItem(pen=pg.mkPen(fitClr, width=4.5)) + + # set line plot data + self.fitLinePlot.setData(fitline, name=feqn) + + # add line plot to the canvas + self.canvas.addItem(self.fitLinePlot) + + def generateMultiColorView(self): + self.multichanneldict = {} + + for n, (colorName, image, rname) in enumerate(zip(cmap_dict.keys(), self.maskImages, self.roiNames)): + low, high = np.min(image), np.max(image) + self.multichanneldict[rname] = { + "ImageName": rname, + "ImageDir": ".", + "Image": image, + "Color": colorName, + "CmapLimits": (low, high), + "Opacity": 1.0, + } + + # print( self.multichanneldict) + self.muli_color_window = MultiChannelWindow(image_dict=self.multichanneldict) + self.muli_color_window.show() + + def exportData(self): + + exporter = pg.exporters.CSVExporter(self.canvas) + # exporter.parameters()['columnMode'] = '(x,y,y,y) for all plots' + file_name = QFileDialog().getSaveFileName(self, + "Save CSV Data", + os.path.join(self.user_wd,"scatter.csv"), + "image file (*csv)") + if file_name[0]: + exporter.export(str(file_name[0])) + self.statusbar.showMessage(f"Data saved to {str(file_name[0])}") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + + def exportAsPNG(self): + file_name = QFileDialog().getSaveFileName(self, + "Save Image", + os.path.join(self.user_wd,"image.png"), + "PNG(*.png);; TIFF(*.tiff);; JPG(*.jpg)" + ) + exporter = pg.exporters.ImageExporter(self.canvas) + + if file_name[0]: + exporter.export(str(file_name[0])) + self.statusbar.showMessage(f"Image saved to {str(file_name[0])}") + self.user_wd = os.path.dirname(file_name[0]) + else: + pass + +class MaskSpecViewer(QtWidgets.QMainWindow): + def __init__(self, xanes_stack=None, xrf_map=None, energy=[]): + super(MaskSpecViewer, self).__init__() + uic.loadUi(os.path.join(ui_path, "uis/MaskedView.ui"), self) + self.user_wd = os.path.abspath("~") + + self.xanes_stack = xanes_stack + self.xrf_map = xrf_map + self.energy = energy + self.xrf_map = self.xanes_stack[-1] + self.view_data() + + # connections + self.sldr_xrf_low.valueChanged.connect(self.create_mask) + self.sldr_xrf_high.valueChanged.connect(self.create_mask) + self.pb_apply_mask.clicked.connect(self.apply_mask_to_xanes) + self.pb_export_mask.clicked.connect(self.export_mask) + self.pb_import_mask.clicked.connect(self.import_a_mask) + self.actionLoad_Energy_List.triggered.connect(self.load_energy) + self.actionLoad_XANES_Stack.triggered.connect(self.load_xanes_stack) + self.actionLoad_XRF_Map.triggered.connect(self.load_xrf_map) + + def view_data(self): + + self.xanes_view.setImage(self.xanes_stack) + self.xanes_view.ui.menuBtn.hide() + self.xanes_view.ui.roiBtn.hide() + (self.dim1, self.dim3, self.dim2) = self.xanes_stack.shape + self.xanes_view.setPredefinedGradient("viridis") + self.xanes_view.setCurrentIndex(self.dim1 // 2) + self.statusbar.showMessage("One image from the XANES stack is used as mask") + self.xrf_view.setImage(self.xrf_map) + self.xrf_view.ui.menuBtn.hide() + self.xrf_view.ui.roiBtn.hide() + self.xrf_view.setPredefinedGradient("viridis") + + self.mask_view.ui.menuBtn.hide() + self.mask_view.ui.roiBtn.hide() + + def create_mask(self): + self.threshold_low = np.around(self.sldr_xrf_low.value() * 0.01, 3) + self.threshold_high = np.around(self.sldr_xrf_high.value() * 0.01, 3) + self.sldr_xrf_low.setMaximum(self.sldr_xrf_high.value() + 1) + self.sldr_xrf_high.setMinimum(self.sldr_xrf_low.value() + 1) + self.norm_xrf_map = remove_nan_inf(self.xrf_map) / remove_nan_inf(self.xrf_map.max()) + self.norm_xrf_map[self.norm_xrf_map < self.threshold_low] = 0 + self.norm_xrf_map[self.norm_xrf_map > self.threshold_high] = 0 + self.xrf_view.setImage(self.norm_xrf_map) + self.le_sldr_vals.setText(str(self.threshold_low) + " to " + str(self.threshold_high)) + self.statusbar.showMessage("New Threshold Applied") + self.xrf_mask = np.where(self.norm_xrf_map > 0, self.norm_xrf_map, 0) + self.xrf_mask[self.xrf_mask > 0] = 1 + self.mask_view.setImage(self.xrf_mask) + + def load_xanes_stack(self): + """loading a new xanes stack""" + filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") + self.file_name = str(filename[0]) + self.xanes_stack = tf.imread(self.file_name).transpose(0, 2, 1) + self.view_data() + + def load_energy(self): + """To load energy list that will be used for plotting the spectra. + number of stack should match length of energy list""" + + file_name = QFileDialog().getOpenFileName(self, "Open energy list", "", "text file (*.txt)") + + try: + self.energy = np.loadtxt(str(file_name[0])) + logger.info("Energy file loaded") + assert len(self.energy) == self.dim1 + self.view_data() + + except OSError: + logger.error("No File selected") + pass + + def load_xrf_map(self): + """To xrf map for masking. If 3D mean will be taken""" + + filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") + self.xrf_file_name = str(filename[0]) + self.xrf_map = tf.imread(self.xrf_file_name) + if self.xrf_map.ndim == 3: + self.xrf_map = self.xrf_map.mean(0).T + + else: + self.xrf_map = self.xrf_map.T + + assert ( + self.dim3, + self.dim2, + ) == self.xrf_map.shape, f"Unexpected image dimensions: {self.xrf_map.shape} vs {(self.dim2,self.dim3)}" + + self.view_data() + self.create_mask() + + def apply_mask_to_xanes(self): + + """Generates a mask with 0 and 1 from the choosen threshold and multply with the xanes stack. + A spectrum will be generated from the new masked stack""" + + self.masked_xanes = self.xanes_stack * self.xrf_mask + self.xanes_view.setImage(self.masked_xanes) + self.xanes_view.setCurrentIndex(self.dim1 // 2) + self.statusbar.showMessage("Mask Applied to XANES") + self.mask_spec = get_mean_spectra(self.masked_xanes) + + if len(self.energy) != 0: + self.xdata = self.energy + else: + self.xdata = np.arange(0, self.dim1) + self.statusbar.showMessage("No Energy List Available; Integer values are used for plotting") + + self.spectrum_view.plot(self.xdata, self.mask_spec, clear=True) + + def import_a_mask(self): + filename = QFileDialog().getOpenFileName(self, "Select image data", "", "image file(*tiff *tif )") + xrf_file_name = str(filename[0]) + self.xrf_mask = tf.imread(xrf_file_name).T + self.statusbar.showMessage("A New Mask Imported") + self.mask_view.setImage(self.xrf_mask) + self.apply_mask_to_xanes() + + def export_mask(self): + try: + file_name = QFileDialog().getSaveFileName(self, "Save image data", "", "image file(*tiff *tif )") + tf.imsave(str(file_name[0]) + ".tiff", self.xrf_mask.T) + logger.info(f"Updated Image Saved: {str(file_name[0])}") + self.statusbar.showMessage("Mask Exported") + except Exception: + logger.error("No file to save") + pass + +class StackInfo(QtWidgets.QMainWindow): + def __init__(self, text_to_write: str = " "): + super(StackInfo, self).__init__() + uic.loadUi(os.path.join(ui_path, "uis/log.ui"), self) + self.user_wd = os.path.abspath("~") + + self.text_to_write = text_to_write + self.pte_run_cmd.setPlainText(self.text_to_write) + + # connections + self.pb_save_cmd.clicked.connect(self.save_file) + self.pb_clear_cmd.clicked.connect(self.clear_text) + + def save_file(self): + S__File = QFileDialog.getSaveFileName(None, "SaveFile", "/", "txt Files (*.txt)") + + Text = self.pte_run_cmd.toPlainText() + if S__File[0]: + with open(S__File[0], "w") as file: + file.write(Text) + + def clear_text(self): + self.pte_run_cmd.clear() + +class MultiChannelWindow(QtWidgets.QMainWindow): + def __init__(self, image_dict=None): + super(MultiChannelWindow, self).__init__() + if image_dict is None: + image_dict = {} + uic.loadUi(os.path.join(ui_path, "uis/mutlichannel.ui"), self) + self.user_wd = os.path.abspath("~") + + self.canvas = self.img_view.addPlot(title="") + self.canvas.getViewBox().invertY(True) + self.canvas.setAspectLocked(True) + self.cb_choose_color.addItems([i for i in cmap_dict.keys()]) + #self.canvas.set + + self.image_dict = image_dict + self.buildFromDictionary() + + # connections + self.actionLoad.triggered.connect(self.createMuliColorAndList) + self.actionLoad_Stack.triggered.connect(self.createMuliColorAndList) + self.actionSave_Stack_tiff.triggered.connect(self.saveTiffData) + self.cb_choose_color.currentTextChanged.connect(self.updateImageDictionary) + self.pb_update_low_high.clicked.connect(self.updateImageDictionary) + self.listWidget.itemClicked.connect(self.editImageProperties) + self.listWidget.itemDoubleClicked.connect(self.showOneImageOnly) + self.pb_show_selected.clicked.connect(self.showOneImageOnly) + self.pb_show_all.clicked.connect(self.showAllItems) + self.actionLoad_State_File.triggered.connect(self.importState) + self.actionSave_State.triggered.connect(self.exportState) + self.actionSave_View.triggered.connect(self.saveImage) + + def buildFromDictionary(self): + if self.image_dict is not None: + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + else: + pass + + def generateImageDictionary(self): + """Creates a dictionary contains image path, color scheme chosen, throshold limits etc. + when user edits the parameters dictionary will be updated and unwrapped for display later. + This dictionary is saved as json file while saving the state. Two image loading options are possible. + User can either select multiple 2D array images or one 3D array (stack)""" + + clickedAction = self.sender() + + if clickedAction.text() == "Load Images": + # multiple images are selected + self.loadMultipleImageFiles() + + elif clickedAction.text() == "Load Stack": + # an image stack is selected + self.loadAsStack() + + def loadMultipleImageFiles(self): + + filter = "TIFF (*.tiff);;TIF (*.tif)" + QtWidgets.QFileDialog().setFileMode(QtWidgets.QFileDialog.ExistingFiles) + # choose mutliple tiff files + names = QtWidgets.QFileDialog().getOpenFileNames(self, "Open files", " ", filter) + if names[0]: + self.image_dict = {} + # select the file directory. Image files are expected to be in the same folder + self.imageDir = os.path.dirname(names[0][0]) + + # create the dictionary + for colorName, image in zip(cmap_dict.keys(), names[0]): + # squeeze to allow with pseudo 3D axis from some tomo recon (eg. 1, 100,100 array) + im_array = np.squeeze(tf.imread(image)) + # set values for thresholding as image min and max + low, high = np.min(im_array), np.max(im_array) + # name of the tiff file is chosen as key for the dictionary, + # inner keys are properties set for that image + im_name = os.path.basename(image) + # construct the dictionary + self.image_dict[f"{os.path.basename(image)}"] = { + "ImageName": im_name, + "ImageDir": self.imageDir, + "Image": im_array, + "Color": colorName, + "CmapLimits": (low, high), + "Opacity": 1.0, + } + else: + pass + + def loadAsStack(self): + """construct the dictionary with image +number as the key. + All other steps are similar to the loadMultipleImageFiles function""" + + filter = "TIFF (*.tiff);;TIF (*.tif)" + file_name = QtWidgets.QFileDialog().getOpenFileName( + self, "Open a Stack", "", "TIFF(*tiff *tif);;all_files (*)", filter + ) + if file_name[0]: + self.imageDir = os.path.dirname(file_name[0]) + self.image_dict = {} + im_stack = np.squeeze(tf.imread(file_name[0])) + # asset the file is a stack + assert im_stack.ndim == 3, "Not a stack" + # construct the dictionary + for n, (colorName, image) in enumerate(zip(cmap_dict.keys(), im_stack)): + low, high = np.min(image), np.max(image) + self.image_dict[f"Image {n+1}"] = { + "ImageName": f"Image {n+1}", + "ImageDir": self.imageDir, + "Image": image, + "Color": colorName, + "CmapLimits": (low, high), + "Opacity": 1.0, + } + + def loadAnImage(self, image, colormap, cmap_limits, opacity=1): + """load single image and colorbar to the widget. This function will be looped for + multiple images later + """ + # get pg image item + img = pg.ImageItem() + # add image to the graphicsview widget + self.canvas.addItem(img) + # set the color map + cmap = pg.ColorMap(pos=np.linspace(0, 1, len(colormap)), color=colormap) + # image = np.squeeze(tf.imread(image_path)) + # set image to the image item with cmap + img.setImage(np.array(image), lut=cmap.getLookupTable(), opacity=opacity) + + # set colorbar for thresholding + bar = pg.ColorBarItem(values=cmap_limits, cmap=cmap, limits=(0, None), orientation="vertical") + bar.setImageItem(img) + # set composition mode to plus for overlaying + img.setCompositionMode(QtGui.QPainter.CompositionMode_Plus) + + def createMultiColorView(self, image_dictionary): + """Function creates multi color image view by taking image + data and parameters from the dictionary""" + + # clear the plots and list in case of re-loading + self.canvas.clear() + self.listWidget.clear() + + # display individual images in for loop + for path_and_color in image_dictionary.values(): + self.loadAnImage( + path_and_color["Image"], + cmap_dict[path_and_color["Color"]], + path_and_color["CmapLimits"], + path_and_color["Opacity"], + ) + + def showOneImageOnly(self): + editItem = self.listWidget.currentItem() + editRow = self.listWidget.currentRow() + for i in range(self.listWidget.count()): + if self.listWidget.item(i) == editItem: + editItemName = self.listWidget.item(i).text().split(",")[0] + self.image_dict[editItemName]["Opacity"] = 1 + + elif self.listWidget.item(i) != editItem: + editItemName = self.listWidget.item(i).text().split(",")[0] + self.image_dict[editItemName]["Opacity"] = 0 + + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + self.listWidget.setCurrentRow(editRow) + + def showAllItems(self): + + editItem = self.listWidget.currentItem() + editRow = self.listWidget.currentRow() + for i in range(self.listWidget.count()): + editItemName = self.listWidget.item(i).text().split(",")[0] + self.image_dict[editItemName]["Opacity"] = 1 + + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + self.listWidget.setCurrentRow(editRow) + + def displayImageNames(self, image_dictionary): + """Populate the list widget table with image name and color used to plot, + using image dictionary input""" + + for im_name, vals in image_dictionary.items(): + self.listWidget.addItem(f"{im_name},{vals['Color']}") + self.listWidget.setCurrentRow(0) + + def createMuliColorAndList(self): + """Finally Load Images and poplulate the list widget from the dictionary""" + with pg.BusyCursor(): # gives the circle showing gui is doing something + self.generateImageDictionary() + if self.image_dict: + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + + else: + pass + + def sliderSetUp(self, im_array): + """Setting the slider min and max from image values""" + + low = (np.min(im_array) / np.max(im_array)) * 100 + self.sldr_low.setMaximum(100) + self.sldr_low.setMinimum(low) + self.sldr_high.setMaximum(100) + self.sldr_high.setMinimum(low) + + def editImageProperties(self, item): + """function to control the assigned properties such as color, + threshold limits, opacity etc of a single image selected using the list widget item""" + + editItem = item.text() + # get the dictionary key from item text + editItemName = editItem.split(",")[0] + editItemColor = editItem.split(",")[1] + im_array = self.image_dict[editItemName]["Image"] + self.sliderSetUp(im_array) + setValLow = (self.image_dict[editItemName]["CmapLimits"][0] * 100) / np.max(im_array) + setValHigh = (self.image_dict[editItemName]["CmapLimits"][1] * 100) / np.max(im_array) + setOpacity = self.image_dict[editItemName]["Opacity"] * 100 + self.sldr_low.setValue(int(setValLow)) + self.sldr_high.setValue(int(setValHigh)) + self.sldr_opacity.setValue(int(setOpacity)) + self.low_high_vals.setText(f"low:{self.sldr_low.value()}," f"high:{self.sldr_high.value()}") + self.cb_choose_color.setCurrentText(editItemColor) + + def updateImageDictionary(self): + newColor = self.cb_choose_color.currentText() + editItem = self.listWidget.currentItem().text() + editRow = self.listWidget.currentRow() + editItemName = editItem.split(",")[0] + self.imageDir = self.image_dict[editItemName]["ImageDir"] + im_array = self.image_dict[editItemName]["Image"] + self.sliderSetUp(im_array) + cmap_limits = ( + self.sldr_low.value() * np.max(im_array) / 100, + self.sldr_high.value() * np.max(im_array) / 100, + ) + self.low_high_vals.setText(f"low:{cmap_limits[0]:.3f},high:{cmap_limits[1]:.3f}") + opacity = self.sldr_opacity.value() / 100 + self.opacity_val.setText(str(opacity)) + self.image_dict[editItemName] = { + "ImageName": editItemName, + "ImageDir": self.imageDir, + "Image": im_array, + "Color": newColor, + "CmapLimits": cmap_limits, + "Opacity": opacity, + } + + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + self.listWidget.setCurrentRow(editRow) + + def exportState(self): + + file_name = QtWidgets.QFileDialog().getSaveFileName( + self, "Save Current State", "multicolor_params.json", "json file(*json)" + ) + """ + for val in self.image_dict.values(): + val['CmapLimits'] = json.dumps(str(val['CmapLimits'])) + """ + + if file_name[0]: + + with open(f"{file_name[0]}", "w") as fp: + json.dump(self.image_dict, fp, indent=4, cls=jsonEncoder) + + else: + pass + + def importState(self): + file_name = QtWidgets.QFileDialog().getOpenFileName( + self, "Open a State File", "", "json file(*json);;all_files (*)" + ) + if file_name[0]: + with open(file_name[0], "r") as fp: + self.image_dict = json.load(fp) + + self.createMultiColorView(self.image_dict) + self.displayImageNames(self.image_dict) + else: + pass + + def saveImage(self): + file_name = QtWidgets.QFileDialog().getSaveFileName( + self, "Save Image", "multicolor_image.png", "PNG(*.png);; TIFF(*.tiff);; JPG(*.jpg)" + ) + exporter = pg.exporters.ImageExporter(self.canvas.getViewBox()) + exporter.export(file_name[0]) + + def saveTiffData(self): + file_name = QtWidgets.QFileDialog().getSaveFileName(self, "Save Image", 'stack_image_data.tiff', + 'TIFF(*.tiff)') + saveStack = [image_property['Image'] for image_property in self.image_dict.values()] + print(np.shape(saveStack)) + + if file_name[0]: + tf.imsave(file_name[0], saveStack) + else: + return + +class MultiXANESWindow(MultiChannelWindow): + + def __init__(self, image_dict=None, spec_df=None): + super().__init__(image_dict=None) + + self.image_dict = image_dict + self.spec_df = spec_df + + uic.loadUi(os.path.join(ui_path, 'uis/MultiImageSpectrumView.ui'), self) + self.user_wd = os.path.abspath("~") + # Copy from MultiChannelWindow Start here + self.canvas = self.img_view.addPlot(title="") + self.canvas.getViewBox().invertY(True) + #self.canvas.setZValue(-10) + self.canvas.setAspectLocked(True) + self.cb_choose_color.addItems([i for i in cmap_dict.keys()]) + #self.canvas.getViewBox().setBackgroundColor(pg.mkColor(222,222,222)) + #self.canvas.getViewBox().setOpacity(0.5) + + self.image_dict = image_dict + self.buildFromDictionary() + + self.actionLoad.triggered.connect(self.createMuliColorAndList) + self.actionLoad_Stack.triggered.connect(self.createMuliColorAndList) + self.cb_choose_color.currentTextChanged.connect(self.updateImageDictionary) + self.pb_update_low_high.clicked.connect(self.updateImageDictionary) + self.listWidget.itemClicked.connect(self.editImageProperties) + self.listWidget.itemDoubleClicked.connect(self.showOneImageOnly) + self.pb_show_selected.clicked.connect(self.showOneImageOnly) + self.pb_show_all.clicked.connect(self.showAllItems) + self.actionLoad_State_File.triggered.connect(self.importState) + self.actionSave_State.triggered.connect(self.exportState) + self.actionSave_View.triggered.connect(self.saveImage) + # Copy from MultiChannelWindow End here + self.actionSave_Spectrum_Data.triggered.connect(self.exportDisplayedSpectra) + self.listWidget_Spectrum.itemClicked.connect(self.plotNormSpectrum) + self.pb_apply_xanes_norm.clicked.connect(lambda: self.updateSpecData(plotNorm=True)) + self.createMultiSpectrumLibrary() + + [dsb.valueChanged.connect(lambda: self.updateSpecData()) for dsb in + [self.dsb_norm_Eo, self.dsb_norm_pre1, self.dsb_norm_pre2, self.dsb_norm_post1, + self.dsb_norm_post2, self.sb_norm_order]] + + def createSpectrumPropertyDict(self, specName, xdata, ydata, e0, pre1, pre2, norm1, norm2, normOrder): + SingleSpecProperty = {'Name': specName, + 'Data': (xdata, ydata), + 'NormParams': [e0, pre1, pre2, norm1, norm2, normOrder]} + + return SingleSpecProperty + + def createMultiSpectrumLibrary(self): + self.spec_dict = {} + column_names = self.spec_df.columns + spec_array = self.spec_df.to_numpy() + energy = spec_array[:, 0] + for i in range(self.spec_df.shape[1]): + if i != 0: + specData = spec_array[:, i] + e0_init = energy[np.argmax(np.gradient(specData))] + + pre1, pre2, post1, post2 = xanesNormalization( + energy, + specData, + e0=e0_init, + step=None, + nnorm=1, + nvict=0, + method = "guess" + ) + + self.spec_dict[column_names[i]] = self.createSpectrumPropertyDict(column_names[i], energy, specData, + e0_init, pre1, pre2, post1, post2, 1) + self.nomalizeSpectraAndPlot(self.spec_dict) + self.spectrumDictToListWidget() + + def nomalizeSpectraAndPlot(self, spectrumParamDict): + # print(self.spec_dict) + try: + self.spectrum_view.clear() + except: + pass + self.spectrum_view.setLabel("bottom", "Energy") + self.spectrum_view.setLabel("left", "Intensity") + self.spectrum_view.addLegend() + plt_colors = ['r', 'g', (31, 81, 255), 'c', 'm', 'y', 'w'] + for n, params in enumerate(spectrumParamDict.values()): + e0_ = params['NormParams'][0] + pre1_ = params['NormParams'][1] + pre2_ = params['NormParams'][2] + norm1_ = params['NormParams'][3] + norm2_ = params['NormParams'][4] + normOrder_ = params['NormParams'][5] + + preLine, postLine, self.normData = xanesNormalization( + params['Data'][0], + params['Data'][1], + e0=e0_, + step=None, + nnorm=normOrder_, + nvict=0, + pre1=pre1_, + pre2=pre2_, + norm1=norm1_, + norm2=norm2_ + ) + + # 'NormParams': (e0, pre1, pre2, norm1, norm2, normOrder)} + self.spectrum_view.plot(params['Data'][0], self.normData, + pen=pg.mkPen(plt_colors[n], width=2), + name=f"Norm._{params['Name']}") + + def loadAndPlotSpectrumData(self): + filter = 'txt (*.tiff);;csv (*.csv)' + file_name = QtWidgets.QFileDialog().getOpenFileName(self, "Open a spectrum file", '', + 'txt (*.tiff);;csv (*.csv);;all_files (*)', filter) + + if file_name[0]: + self.spec_df = pd.read_csv(file_name[0], index_col=None) + # print(self.spec_df.head()) + # print(self.spec_df.shape[1]) + self.createMultiSpectrumLibrary() + else: + return + + def spectrumDictToListWidget(self): + for params in self.spec_dict.values(): + # Creates a QListWidgetItem + specItem = QtWidgets.QListWidgetItem() + + # Setting QListWidgetItem Text + specItem.setText(params['Name']) + + # Setting your QListWidgetItem Data + specItem.setData(QtCore.Qt.UserRole, params) + + # Add the new rule to the QListWidget + self.listWidget_Spectrum.addItem(specItem) + + def plotNormSpectrum(self, item): + self.selectedItem = item + self.editItemName = self.selectedItem.text() + self.editItemData = self.selectedItem.data(QtCore.Qt.UserRole) + + e0_ = self.editItemData['NormParams'][0] + pre1_ = self.editItemData['NormParams'][1] + pre2_ = self.editItemData['NormParams'][2] + norm1_ = self.editItemData['NormParams'][3] + norm2_ = self.editItemData['NormParams'][4] + normOrder_ = self.editItemData['NormParams'][5] + + self.dsb_norm_Eo.setValue(e0_) # loop later + self.dsb_norm_pre1.setValue(pre1_) + self.dsb_norm_pre2.setValue(pre2_) + self.dsb_norm_post1.setValue(norm1_) + self.dsb_norm_post2.setValue(norm2_) + self.sb_norm_order.setValue(normOrder_) + + preLine, postLine, normSpec = xanesNormalization( + self.editItemData['Data'][0], + self.editItemData['Data'][1], + e0=e0_, + step=None, + nnorm=normOrder_, + nvict=0, + pre1=pre1_, + pre2=pre2_, + norm1=norm1_, + norm2=norm2_ + ) + + self.spectrum_view.clear() + self.spectrum_view.plot(self.editItemData['Data'][0], self.editItemData['Data'][1], + title=f"Normalization Plot_{self.editItemData['Name']}", + pen=pg.mkPen('y', width=2), name=self.editItemData['Name']) + self.spectrum_view.plot(self.editItemData['Data'][0], preLine, pen=pg.mkPen('c', width=2), name='Pre') + self.spectrum_view.plot(self.editItemData['Data'][0], postLine, pen=pg.mkPen('m', width=2), name='Norm') + + # def updateNormParamaters(self): + + def updateSpecData(self, plotNorm=False): + + # loop later + self.editItemData['NormParams'][0] = self.dsb_norm_Eo.value() + self.editItemData['NormParams'][1] = self.dsb_norm_pre1.value() + self.editItemData['NormParams'][2] = self.dsb_norm_pre2.value() + self.editItemData['NormParams'][3] = self.dsb_norm_post1.value() + self.editItemData['NormParams'][4] = self.dsb_norm_post2.value() + self.editItemData['NormParams'][5] = self.sb_norm_order.value() + + self.spec_dict[self.editItemName] = self.editItemData + self.selectedItem.setData(QtCore.Qt.UserRole, self.editItemData) + if plotNorm: + self.nomalizeSpectraAndPlot(self.spec_dict) + else: + self.plotNormSpectrum(self.selectedItem) + + def exportDisplayedSpectra(self): + exporter = pg.exporters.CSVExporter(self.spectrum_view.plotItem) + exporter.parameters()['columnMode'] = '(x,y,y,y) for all plots' + file_name = QFileDialog().getSaveFileName(self, "save spectra", 'xanes.csv', 'spectra (*csv)') + if file_name[0]: + exporter.export(str(file_name[0])) + else: + self.statusbar_main.showMessage('Saving cancelled') + pass + + +def start_xmidas(): + def formatter(prog): + # Set maximum width such that printed help mostly fits in the RTD theme code block (documentation). + return argparse.RawDescriptionHelpFormatter(prog, max_help_position=20, width=90) + ''' + parser = argparse.ArgumentParser( + description=f"XMidas: v{__version__}", + formatter_class=formatter, + ) + parser.parse_args() + ''' + logger.setLevel(logging.INFO) + formatter = logging.Formatter(fmt="%(asctime)s : %(levelname)s : %(message)s") + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(formatter) + stream_handler.setLevel(logging.INFO) + if logger.hasHandlers(): + logger.handlers.clear() + logger.addHandler(stream_handler) + + if version.parse(PYQT_VERSION_STR) >= version.parse("5.14"): + QApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) + + app = QtWidgets.QApplication(sys.argv) + # app.setAttribute(QtCore.Qt.AA_Use96Dpi) + window = midasWindow() + window.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + start_xmidas() diff --git a/xmidas/uis/ClusterView.ui b/xmidas/uis/ClusterView.ui index 6f5fa2e..0fea754 100644 --- a/xmidas/uis/ClusterView.ui +++ b/xmidas/uis/ClusterView.ui @@ -1,266 +1,266 @@ - - - MainWindow - - - - 0 - 0 - 979 - 815 - - - - - 0 - 0 - - - - MainWindow - - - background-color: rgb(240, 240, 240); -font: 10pt "Segoe UI"; - - - - QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -} - - - - - - - - - - - - - Composite Map - - - Qt::AlignCenter - - - - - - - Opan as RGBCMY Image - - - - - - - - - - - - - - - - - Nth Cluster - - - Qt::AlignCenter - - - - - - - - - - - - - 0 - 0 - - - - 0 - - - 20 - - - 1 - - - 1 - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - font: 12pt "Segoe UI"; - - - 1/3 - - - - - - - - - - - - - - - 100 - - - 100 - - - - - - - font: 75 12pt "MS Shell Dlg 2"; - - - Cluster Spectrum - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - - - Show all - - - - - - - - - QAbstractScrollArea::AdjustToContents - - - - - - - - - - - - - 0 - 0 - 979 - 23 - - - - - Image - - - - - - - - Spectrum - - - - - - - Options - - - - - - - - - - - Save All Clusters - - - - - Save Current Cluster - - - - - Save Composite Map as Masks - - - - - Save All Cluster Spectra - - - - - Save Current Spectrum - - - - - Save Results as a Folder - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 979 + 815 + + + + + 0 + 0 + + + + MainWindow + + + background-color: rgb(240, 240, 240); +font: 10pt "Segoe UI"; + + + + QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +} + + + + + + + + + + + + + Composite Map + + + Qt::AlignCenter + + + + + + + Opan as RGBCMY Image + + + + + + + + + + + + + + + + + Nth Cluster + + + Qt::AlignCenter + + + + + + + + + + + + + 0 + 0 + + + + 0 + + + 20 + + + 1 + + + 1 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + font: 12pt "Segoe UI"; + + + 1/3 + + + + + + + + + + + + + + + 100 + + + 100 + + + + + + + font: 75 12pt "MS Shell Dlg 2"; + + + Cluster Spectrum + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + Show all + + + + + + + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + + + + 0 + 0 + 979 + 23 + + + + + Image + + + + + + + + Spectrum + + + + + + + Options + + + + + + + + + + + Save All Clusters + + + + + Save Current Cluster + + + + + Save Composite Map as Masks + + + + + Save All Cluster Spectra + + + + + Save Current Spectrum + + + + + Save Results as a Folder + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/ComponentScatterPlot.ui b/xmidas/uis/ComponentScatterPlot.ui index 2e4519b..ee95575 100644 --- a/xmidas/uis/ComponentScatterPlot.ui +++ b/xmidas/uis/ComponentScatterPlot.ui @@ -1,195 +1,195 @@ - - - ScatterPlot - - - - 0 - 0 - 1034 - 859 - - - - Correlation Plot - - - font: 12pt "MS Shell Dlg 2"; - - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Image - - - Qt::AlignCenter - - - - - - - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Image - - - Qt::AlignCenter - - - - - - - - - - - - - - - Qt::LeftToRight - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - Apply - - - - - - - Scatter View of - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - Add A Line - - - - - - - Create A Mask - - - - - - - Reset Mask - - - - - - - Apply Mask - - - - - - - - - - - 0 - 0 - 1034 - 30 - - - - - File - - - - - - - - - - Save Plot - - - - - Save Images - - - - - - ImageView - QGraphicsView -
pyqtgraph
-
- - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + ScatterPlot + + + + 0 + 0 + 1034 + 859 + + + + Correlation Plot + + + font: 12pt "MS Shell Dlg 2"; + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Image + + + Qt::AlignCenter + + + + + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Image + + + Qt::AlignCenter + + + + + + + + + + + + + + + Qt::LeftToRight + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Apply + + + + + + + Scatter View of + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + Add A Line + + + + + + + Create A Mask + + + + + + + Reset Mask + + + + + + + Apply Mask + + + + + + + + + + + 0 + 0 + 1034 + 30 + + + + + File + + + + + + + + + + Save Plot + + + + + Save Images + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+ + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/ComponentView.ui b/xmidas/uis/ComponentView.ui index df74ea3..c7aca47 100644 --- a/xmidas/uis/ComponentView.ui +++ b/xmidas/uis/ComponentView.ui @@ -1,296 +1,303 @@ - - - MainWindow - - - - 0 - 0 - 920 - 858 - - - - MainWindow - - - background-color: rgb(240, 240, 240); - - - - - - - - - - - - - - - - - - - - - Components - - - Qt::AlignCenter - - - - - - - - - - - - - - - 0 - 0 - - - - QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 0, 127); -} - - - 20 - - - 1 - - - 1 - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - font: 12pt "Segoe UI"; - - - 1/3 - - - - - - - - - - - - - - - - Component Masks - - - Qt::AlignCenter - - - - - - - - - - - - - Show All Spectra - - - - - - - Opan as RGBCMY Image - - - - - - - - - - - - - - - - Masked Spectrum - - - Qt::AlignCenter - - - - - - - - - - - - - - - - Component Spectrum - - - Qt::AlignCenter - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - Open ScatterPlot - - - - - - - - - - - - 0 - 0 - 920 - 26 - - - - - File - - - - - - - - - Save - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 920 + 858 + + + + MainWindow + + + background-color: rgb(240, 240, 240); + + + + + + + + + + + + + + + + + + + + + Components + + + Qt::AlignCenter + + + + + + + + + + + + + + + 0 + 0 + + + + QSlider::groove:horizontal { +border: 1px solid #bbb; +background: white; +height: 10px; +border-radius: 4px; +} + +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #66e, stop: 1 #bbf); +background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, + stop: 0 #bbf, stop: 1 #55f); +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::add-page:horizontal { +background: #fff; +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::handle:horizontal { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #eee, stop:1 #ccc); +border: 1px solid #777; +width: 13px; +margin-top: -2px; +margin-bottom: -2px; +border-radius: 4px; +} + +QSlider::handle:horizontal:hover { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #fff, stop:1 #ddd); +border: 1px solid #444; +border-radius: 2px; +} + +QSlider::sub-page:horizontal:disabled { +background: #bbb; +border-color: #999; +} + +QSlider::add-page:horizontal:disabled { +background: #eee; +border-color: #999; +} + +QSlider::handle:horizontal:disabled { +background: #eee; +border: 1px solid #aaa; +border-radius: 4px; +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 0, 127); +} + + + 20 + + + 1 + + + 1 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + font: 12pt "Segoe UI"; + + + 1/3 + + + + + + + + + + + + + + + + Component Masks + + + Qt::AlignCenter + + + + + + + + + + + + + Show All Spectra + + + + + + + Open as RGBCMY Image + + + + + + + + + + + + + + + + Masked Spectrum + + + Qt::AlignCenter + + + + + + + + + + + + + + + + Component Spectrum + + + Qt::AlignCenter + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + Open ScatterPlot + + + + + + + Open XANES Norm. View + + + + + + + + + + + + 0 + 0 + 920 + 26 + + + + + File + + + + + + + + + Save + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/Log.ui b/xmidas/uis/Log.ui index 92ca4ec..6ff023b 100644 --- a/xmidas/uis/Log.ui +++ b/xmidas/uis/Log.ui @@ -1,68 +1,68 @@ - - - MainWindow - - - - 0 - 0 - 624 - 759 - - - - Log - - - - - - - font: 10pt "Segoe UI"; - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - QPlainTextEdit::NoWrap - - - start typing here.. - - - - - - - - - Save - - - - - - - Clear - - - - - - - - - - - 0 - 0 - 624 - 26 - - - - - - - - + + + MainWindow + + + + 0 + 0 + 624 + 759 + + + + Log + + + + + + + font: 10pt "Segoe UI"; + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + QPlainTextEdit::NoWrap + + + start typing here.. + + + + + + + + + Save + + + + + + + Clear + + + + + + + + + + + 0 + 0 + 624 + 26 + + + + + + + + diff --git a/xmidas/uis/MaskedView.ui b/xmidas/uis/MaskedView.ui index a8c40db..7a08678 100644 --- a/xmidas/uis/MaskedView.ui +++ b/xmidas/uis/MaskedView.ui @@ -1,352 +1,352 @@ - - - MainWindow - - - - 0 - 0 - 930 - 779 - - - - MaskedXANES - - - - - - - - - - - - color: rgb(255, 0, 0); -font: 12pt "MS Shell Dlg 2"; - - - XANES Stack - - - Qt::AlignCenter - - - - - - - - - - - - - - font: 12pt "MS Shell Dlg 2"; -color: rgb(255, 0, 0); - - - Mask - - - Qt::AlignCenter - - - - - - - - - - - - 0 - - - - - A Tiff Mask with same dimensions - - - background-color: rgb(170, 255, 255); -font: 10pt "MS Shell Dlg 2"; - - - Import Mask - - - - - - - Save Mask tiff - - - background-color: rgb(170, 255, 255); -font: 10pt "MS Shell Dlg 2"; - - - Export Mask - - - - - - - Apply the mask to XANES - - - background-color: rgb(170, 255, 255); -font: 10pt "MS Shell Dlg 2"; - - - Apply - - - - - - - - - - - font: 75 12pt "MS Shell Dlg 2"; -color: rgb(255, 0, 0); - - - Mean Spectrum - - - Qt::AlignCenter - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - font: 12pt "MS Shell Dlg 2"; -color: rgb(255, 0, 0); - - - XRF Map - - - Qt::AlignCenter - - - - - - - font: 10pt "MS Shell Dlg 2"; -color: rgb(255, 0, 0); - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - - Qt::NoFocus - - - 100 - - - 5 - - - 5 - - - 0 - - - 0 - - - true - - - Qt::Vertical - - - QSlider::TicksAbove - - - 5 - - - - - - - - - - - Qt::NoFocus - - - 100 - - - 5 - - - 5 - - - 100 - - - 100 - - - true - - - Qt::Vertical - - - false - - - false - - - QSlider::TicksBelow - - - 5 - - - - - - - - - - - - - - - - - 0 - 0 - 930 - 26 - - - - - File - - - - - - - - - - - - - - Save XANES Stack - - - - - Save XRF Map - - - - - Save Spectrum - - - - - Load Energy List - - - - - Load XRF Map - - - - - Load XANES Stack - - - - - - ImageView - QGraphicsView -
pyqtgraph
-
- - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 930 + 779 + + + + MaskedXANES + + + + + + + + + + + + color: rgb(255, 0, 0); +font: 12pt "MS Shell Dlg 2"; + + + XANES Stack + + + Qt::AlignCenter + + + + + + + + + + + + + + font: 12pt "MS Shell Dlg 2"; +color: rgb(255, 0, 0); + + + Mask + + + Qt::AlignCenter + + + + + + + + + + + + 0 + + + + + A Tiff Mask with same dimensions + + + background-color: rgb(170, 255, 255); +font: 10pt "MS Shell Dlg 2"; + + + Import Mask + + + + + + + Save Mask tiff + + + background-color: rgb(170, 255, 255); +font: 10pt "MS Shell Dlg 2"; + + + Export Mask + + + + + + + Apply the mask to XANES + + + background-color: rgb(170, 255, 255); +font: 10pt "MS Shell Dlg 2"; + + + Apply + + + + + + + + + + + font: 75 12pt "MS Shell Dlg 2"; +color: rgb(255, 0, 0); + + + Mean Spectrum + + + Qt::AlignCenter + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + font: 12pt "MS Shell Dlg 2"; +color: rgb(255, 0, 0); + + + XRF Map + + + Qt::AlignCenter + + + + + + + font: 10pt "MS Shell Dlg 2"; +color: rgb(255, 0, 0); + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + Qt::NoFocus + + + 100 + + + 5 + + + 5 + + + 0 + + + 0 + + + true + + + Qt::Vertical + + + QSlider::TicksAbove + + + 5 + + + + + + + + + + + Qt::NoFocus + + + 100 + + + 5 + + + 5 + + + 100 + + + 100 + + + true + + + Qt::Vertical + + + false + + + false + + + QSlider::TicksBelow + + + 5 + + + + + + + + + + + + + + + + + 0 + 0 + 930 + 26 + + + + + File + + + + + + + + + + + + + + Save XANES Stack + + + + + Save XRF Map + + + + + Save Spectrum + + + + + Load Energy List + + + + + Load XRF Map + + + + + Load XANES Stack + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+ + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/MultiImageSpectrumView.ui b/xmidas/uis/MultiImageSpectrumView.ui new file mode 100644 index 0000000..a418c19 --- /dev/null +++ b/xmidas/uis/MultiImageSpectrumView.ui @@ -0,0 +1,734 @@ + + + MainWindow + + + + 0 + 0 + 1182 + 821 + + + + Chemical Map Spectrum View + + + font: 12pt "Segoe UI" + + + + QPushButton { +border: 1px solid #555; +border-radius: 5px; +background: qradialgradient(cx: 0.3, cy: -0.1, +fx: 0.7, fy: 0.1, +radius: 1, stop: 0 #fff, stop: 1 #888); +background-color: rgb(170, 255, 255); +} + +QPushButton:hover{ + background-color: rgb(255, 255, 0); + } + +QPushButton:pressed{ + background-color: rgb(0,255, 0); + } + +font: 12pt "Segoe UI"; + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + + + + Edit + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + 0 + 212 + 85 + + + + Thresholding + + + + + + + + + 0 + 0 + + + + 0,100 + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + click update after making changes + + + 100 + + + 5 + + + 5 + + + 100 + + + 100 + + + true + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 5 + + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + click update after making changes + + + 100 + + + 5 + + + 5 + + + 0 + + + 0 + + + true + + + Qt::Horizontal + + + QSlider::NoTicks + + + 5 + + + + + + + + + + + + + + + 0 + 0 + 212 + 85 + + + + Opacity + + + + + + + + + 0 + 0 + + + + 1 + + + Qt::AlignCenter + + + + + + + click update after making changes + + + 100 + + + 10 + + + 100 + + + Qt::Horizontal + + + + + + + + + + + + + execute above changes to the selected item + + + Update + + + + + + + + + Show Selected + + + + + + + Show All + + + + + + + + + + 0 + 0 + + + + change properties of the selected item + + + font: 8pt "Segoe UI"; + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + 0 + 0 + + + + Change Selected To + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + XANES Normalization Parameters + + + + + + + 0 + 0 + + + + eV + + + -500.000000000000000 + + + 500.000000000000000 + + + -10.000000000000000 + + + + + + + + 0 + 0 + + + + eV + + + 0.000000000000000 + + + 1000.000000000000000 + + + 25.000000000000000 + + + + + + + + 0 + 0 + + + + 1 + + + 5 + + + + + + + + 0 + 0 + + + + Post-edge + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Norm. Order + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + eV + + + 2 + + + -500.000000000000000 + + + 500.000000000000000 + + + 1.000000000000000 + + + -50.000000000000000 + + + + + + + + 0 + 0 + + + + eV + + + 0.000000000000000 + + + 1500.000000000000000 + + + 75.000000000000000 + + + + + + + + 0 + 0 + + + + Pre-edge + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Eo + + + + + + + + 0 + 0 + + + + eV + + + 1000.000000000000000 + + + 20000.000000000000000 + + + 7125.000000000000000 + + + + + + + + 0 + 0 + + + + Plot Normalized Spectra + + + + + + + + + + + + + + + + + + 0 + 0 + 1182 + 27 + + + + + Image + + + true + + + + + + + + + + Spectrum + + + + + + + + + + Load Images + + + Select and load multiple tiff images to create a multi color view + + + + + Load 2 + + + + + Load 3 + + + + + Load 4 + + + + + Load 5 + + + + + Load 6 + + + + + Export Image + + + Export the image view as a sinle image file + + + + + Save State File + + + Save the current state of the view. Images and properties are saved + + + + + Load State File + + + Load a state (json file) saved previously. + + + + + Load Stack + + + Load images as a stack of tiff + + + + + Save Displayed Spectra(.CSV) + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/RefChooser.ui b/xmidas/uis/RefChooser.ui index eea416f..26f9474 100644 --- a/xmidas/uis/RefChooser.ui +++ b/xmidas/uis/RefChooser.ui @@ -1,247 +1,252 @@ - - - MainWindow - - - - 0 - 0 - 986 - 985 - - - - ArrowCursor - - - Select References - - - font: 10pt "MS Shell Dlg 2"; - - - - QCheckBox:checked { -color: rgb(255, 75, 52); - background-color: rgb(255, 248, 149); -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -} - -QPushButton:disabled { -background-color: rgb(255, 227, 213); -color: rgb(255, 255, 255); -} - - - - - - - 25 - - - 25 - - - 25 - - - 25 - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - Drag the line or double click on a point to select the parameters - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - Max. No. of Refs - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 1 - - - 10 - - - 2 - - - - - - - - - - - Try All Cobinations - - - - - - - false - - - Fit With Selected - - - - - - - - - - color: rgb(255, 75, 52); -font: 10pt "MS Shell Dlg 2"; - - - N Combinations - - - Qt::AlignCenter - - - - - - - selection-color: rgb(0, 170, 127); - - - 1 - - - - - - - - - Sort by - - - - - - - - Fit Number - - - - - Reduced Chi-Square - - - - - R-Factor - - - - - R-Square - - - - - Chi-Square - - - - - - - - - - - - - - - 0 - 0 - 986 - 22 - - - - - File - - - - - - - - - Export Results (.csv) - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 986 + 985 + + + + ArrowCursor + + + Select References + + + font: 10pt "MS Shell Dlg 2"; + + + + QCheckBox:checked { +color: rgb(255, 75, 52); + background-color: rgb(255, 248, 149); +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +} + +QPushButton:disabled { +background-color: rgb(255, 227, 213); +color: rgb(255, 255, 255); +} + + + + + + + 25 + + + 25 + + + 25 + + + 25 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + Drag the line or double click on a point to select the parameters + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + Max. No. of Refs + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 10 + + + 2 + + + + + + + + + + + Try All Cobinations + + + + + + + false + + + Fit With Selected + + + + + + + + + + color: rgb(255, 75, 52); +font: 10pt "MS Shell Dlg 2"; + + + N Combinations + + + Qt::AlignCenter + + + + + + + selection-color: rgb(0, 170, 127); + + + 1 + + + + + + + + + Sort by + + + + + + + + Reduced Chi-Square + + + + + Score + + + + + Fit Number + + + + + R-Factor + + + + + R-Square + + + + + Chi-Square + + + + + + + + + + + + + + + 0 + 0 + 986 + 27 + + + + + File + + + + + + + + + Export Results (.csv) + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/SVGs/Arrow Down.svg b/xmidas/uis/SVGs/Arrow Down.svg deleted file mode 100644 index cce1eec..0000000 --- a/xmidas/uis/SVGs/Arrow Down.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Arrow Left.svg b/xmidas/uis/SVGs/Arrow Left.svg deleted file mode 100644 index 93a3cd4..0000000 --- a/xmidas/uis/SVGs/Arrow Left.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Arrow Right.svg b/xmidas/uis/SVGs/Arrow Right.svg deleted file mode 100644 index fb1f0e1..0000000 --- a/xmidas/uis/SVGs/Arrow Right.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Arrow Up.svg b/xmidas/uis/SVGs/Arrow Up.svg deleted file mode 100644 index 40dbd57..0000000 --- a/xmidas/uis/SVGs/Arrow Up.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Basket.svg b/xmidas/uis/SVGs/Basket.svg deleted file mode 100644 index e33eea3..0000000 --- a/xmidas/uis/SVGs/Basket.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Book.svg b/xmidas/uis/SVGs/Book.svg deleted file mode 100644 index 27d79f2..0000000 --- a/xmidas/uis/SVGs/Book.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Calendar.svg b/xmidas/uis/SVGs/Calendar.svg deleted file mode 100644 index 8e6ee76..0000000 --- a/xmidas/uis/SVGs/Calendar.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Camera.svg b/xmidas/uis/SVGs/Camera.svg deleted file mode 100644 index 80b9e35..0000000 --- a/xmidas/uis/SVGs/Camera.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Case.svg b/xmidas/uis/SVGs/Case.svg deleted file mode 100644 index 173d397..0000000 --- a/xmidas/uis/SVGs/Case.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Change View.svg b/xmidas/uis/SVGs/Change View.svg deleted file mode 100644 index 9332cfc..0000000 --- a/xmidas/uis/SVGs/Change View.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Check V2.svg b/xmidas/uis/SVGs/Check V2.svg deleted file mode 100644 index c8a5e40..0000000 --- a/xmidas/uis/SVGs/Check V2.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Check.svg b/xmidas/uis/SVGs/Check.svg deleted file mode 100644 index dadeaed..0000000 --- a/xmidas/uis/SVGs/Check.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Chervon Right Circle.svg b/xmidas/uis/SVGs/Chervon Right Circle.svg deleted file mode 100644 index 1492a0b..0000000 --- a/xmidas/uis/SVGs/Chervon Right Circle.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Down Circle.svg b/xmidas/uis/SVGs/Chevron Down Circle.svg deleted file mode 100644 index c7ce17b..0000000 --- a/xmidas/uis/SVGs/Chevron Down Circle.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Down.svg b/xmidas/uis/SVGs/Chevron Down.svg deleted file mode 100644 index 56a3576..0000000 --- a/xmidas/uis/SVGs/Chevron Down.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Left Circle.svg b/xmidas/uis/SVGs/Chevron Left Circle.svg deleted file mode 100644 index a0e84b0..0000000 --- a/xmidas/uis/SVGs/Chevron Left Circle.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Left.svg b/xmidas/uis/SVGs/Chevron Left.svg deleted file mode 100644 index f296cd7..0000000 --- a/xmidas/uis/SVGs/Chevron Left.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Right.svg b/xmidas/uis/SVGs/Chevron Right.svg deleted file mode 100644 index f468a3f..0000000 --- a/xmidas/uis/SVGs/Chevron Right.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Up Circle.svg b/xmidas/uis/SVGs/Chevron Up Circle.svg deleted file mode 100644 index 3ebfe80..0000000 --- a/xmidas/uis/SVGs/Chevron Up Circle.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Chevron Up.svg b/xmidas/uis/SVGs/Chevron Up.svg deleted file mode 100644 index fc00cf6..0000000 --- a/xmidas/uis/SVGs/Chevron Up.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Clock.svg b/xmidas/uis/SVGs/Clock.svg deleted file mode 100644 index 2bc6f3f..0000000 --- a/xmidas/uis/SVGs/Clock.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Close.svg b/xmidas/uis/SVGs/Close.svg deleted file mode 100644 index 5e22958..0000000 --- a/xmidas/uis/SVGs/Close.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Cog.svg b/xmidas/uis/SVGs/Cog.svg deleted file mode 100644 index 9a39f32..0000000 --- a/xmidas/uis/SVGs/Cog.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Cross Circle.svg b/xmidas/uis/SVGs/Cross Circle.svg deleted file mode 100644 index b4d5ebb..0000000 --- a/xmidas/uis/SVGs/Cross Circle.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Download.svg b/xmidas/uis/SVGs/Download.svg deleted file mode 100644 index deeac83..0000000 --- a/xmidas/uis/SVGs/Download.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Edit V2.svg b/xmidas/uis/SVGs/Edit V2.svg deleted file mode 100644 index 49c8796..0000000 --- a/xmidas/uis/SVGs/Edit V2.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Edit.svg b/xmidas/uis/SVGs/Edit.svg deleted file mode 100644 index 7494aef..0000000 --- a/xmidas/uis/SVGs/Edit.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/xmidas/uis/SVGs/File.svg b/xmidas/uis/SVGs/File.svg deleted file mode 100644 index 9dadb9b..0000000 --- a/xmidas/uis/SVGs/File.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Grid.svg b/xmidas/uis/SVGs/Grid.svg deleted file mode 100644 index b9792e5..0000000 --- a/xmidas/uis/SVGs/Grid.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Heart.svg b/xmidas/uis/SVGs/Heart.svg deleted file mode 100644 index 5f71778..0000000 --- a/xmidas/uis/SVGs/Heart.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Image.svg b/xmidas/uis/SVGs/Image.svg deleted file mode 100644 index 8399140..0000000 --- a/xmidas/uis/SVGs/Image.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Link.svg b/xmidas/uis/SVGs/Link.svg deleted file mode 100644 index fba631d..0000000 --- a/xmidas/uis/SVGs/Link.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Location Cursor.svg b/xmidas/uis/SVGs/Location Cursor.svg deleted file mode 100644 index 65858b3..0000000 --- a/xmidas/uis/SVGs/Location Cursor.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Location.svg b/xmidas/uis/SVGs/Location.svg deleted file mode 100644 index 7626878..0000000 --- a/xmidas/uis/SVGs/Location.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Logout.svg b/xmidas/uis/SVGs/Logout.svg deleted file mode 100644 index 04d2dde..0000000 --- a/xmidas/uis/SVGs/Logout.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Mail.svg b/xmidas/uis/SVGs/Mail.svg deleted file mode 100644 index c64f596..0000000 --- a/xmidas/uis/SVGs/Mail.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Menu.svg b/xmidas/uis/SVGs/Menu.svg deleted file mode 100644 index af3578c..0000000 --- a/xmidas/uis/SVGs/Menu.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Message.svg b/xmidas/uis/SVGs/Message.svg deleted file mode 100644 index 0dc9f24..0000000 --- a/xmidas/uis/SVGs/Message.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Microphone.svg b/xmidas/uis/SVGs/Microphone.svg deleted file mode 100644 index d3bdff4..0000000 --- a/xmidas/uis/SVGs/Microphone.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/More.svg b/xmidas/uis/SVGs/More.svg deleted file mode 100644 index ad2af87..0000000 --- a/xmidas/uis/SVGs/More.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Plus Circle.svg b/xmidas/uis/SVGs/Plus Circle.svg deleted file mode 100644 index be293d9..0000000 --- a/xmidas/uis/SVGs/Plus Circle.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Plus.svg b/xmidas/uis/SVGs/Plus.svg deleted file mode 100644 index 9a2a645..0000000 --- a/xmidas/uis/SVGs/Plus.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Power Button.svg b/xmidas/uis/SVGs/Power Button.svg deleted file mode 100644 index 2541ea6..0000000 --- a/xmidas/uis/SVGs/Power Button.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Printer.svg b/xmidas/uis/SVGs/Printer.svg deleted file mode 100644 index 5ac0097..0000000 --- a/xmidas/uis/SVGs/Printer.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Settings.svg b/xmidas/uis/SVGs/Settings.svg deleted file mode 100644 index 28474d0..0000000 --- a/xmidas/uis/SVGs/Settings.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Share.svg b/xmidas/uis/SVGs/Share.svg deleted file mode 100644 index 3fe3197..0000000 --- a/xmidas/uis/SVGs/Share.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Shutter.svg b/xmidas/uis/SVGs/Shutter.svg deleted file mode 100644 index 92d99e7..0000000 --- a/xmidas/uis/SVGs/Shutter.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Star.svg b/xmidas/uis/SVGs/Star.svg deleted file mode 100644 index facd65d..0000000 --- a/xmidas/uis/SVGs/Star.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/Trash.svg b/xmidas/uis/SVGs/Trash.svg deleted file mode 100644 index ec862c9..0000000 --- a/xmidas/uis/SVGs/Trash.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/xmidas/uis/SVGs/Trophy.svg b/xmidas/uis/SVGs/Trophy.svg deleted file mode 100644 index 050c08d..0000000 --- a/xmidas/uis/SVGs/Trophy.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/xmidas/uis/SVGs/User.svg b/xmidas/uis/SVGs/User.svg deleted file mode 100644 index 71bb60b..0000000 --- a/xmidas/uis/SVGs/User.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/xmidas/uis/SVGs/Video.svg b/xmidas/uis/SVGs/Video.svg deleted file mode 100644 index 6d8869f..0000000 --- a/xmidas/uis/SVGs/Video.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/xmidas/uis/Scatter3D.ui b/xmidas/uis/Scatter3D.ui index ca2ab8d..cefddb8 100644 --- a/xmidas/uis/Scatter3D.ui +++ b/xmidas/uis/Scatter3D.ui @@ -1,130 +1,130 @@ - - - MainWindow - - - - 0 - 0 - 658 - 628 - - - - MainWindow - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - 658 - 21 - - - - - View - - - - Plot Background - - - - - - - - - - - File - - - - - - - - - - - - Export - - - - - Save as PNG - - - - - Generate MultiColor Mask - - - - - Black - - - - - Charcol - - - - - White - - - - - Open Images - - - - - Open a Stack - - - - - Export Image - - - - - - GLViewWidget - QGraphicsView -
pyqtgraph.opengl
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 658 + 628 + + + + MainWindow + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 658 + 21 + + + + + View + + + + Plot Background + + + + + + + + + + + File + + + + + + + + + + + + Export + + + + + Save as PNG + + + + + Generate MultiColor Mask + + + + + Black + + + + + Charcol + + + + + White + + + + + Open Images + + + + + Open a Stack + + + + + Export Image + + + + + + GLViewWidget + QGraphicsView +
pyqtgraph.opengl
+
+
+ + +
diff --git a/xmidas/uis/Scatter3D_plotly.ui b/xmidas/uis/Scatter3D_plotly.ui index 1318496..df22d6d 100644 --- a/xmidas/uis/Scatter3D_plotly.ui +++ b/xmidas/uis/Scatter3D_plotly.ui @@ -1,120 +1,120 @@ - - - MainWindow - - - - 0 - 0 - 856 - 628 - - - - MainWindow - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - 856 - 21 - - - - - View - - - - Plot Background - - - - - - - - - - - - - - toolBar - - - TopToolBarArea - - - false - - - - - - - - Export - - - - - Save as PNG - - - - - Generate MultiColor Mask - - - - - Black - - - - - Grey - - - - - White - - - - - - QWebEngineView - QGraphicsView -
QtWebEngineWidgets
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 856 + 628 + + + + MainWindow + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 856 + 21 + + + + + View + + + + Plot Background + + + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + Export + + + + + Save as PNG + + + + + Generate MultiColor Mask + + + + + Black + + + + + Grey + + + + + White + + + + + + QWebEngineView + QGraphicsView +
QtWebEngineWidgets
+
+
+ + +
diff --git a/xmidas/uis/ScatterView.ui b/xmidas/uis/ScatterView.ui index f5394d3..4f0831e 100644 --- a/xmidas/uis/ScatterView.ui +++ b/xmidas/uis/ScatterView.ui @@ -1,249 +1,249 @@ - - - CorrelationPlot - - - - 0 - 0 - 1034 - 859 - - - - Correlation Plot - - - font:10pt "Segoe UI"; - - - - QRadioButton::indicator { - border: 3px solid rgb(150, 150,150); - width: 15px; - height: 15px; - border-radius: 10px; - background: rgb(255, 255, 255); -} -QRadioButton::indicator:hover { - border: 3px solid rgb(58, 66, 81); -} -QRadioButton::indicator:checked { - background: 3px solid rgb(225, 75, 225); - border: 3px solid rgb(255, 252, 255); -} - - - - - - ROI Mask - - - - - - - - color: rgb(255, 0, 0); -background-color: rgb(20, 20, 20); -font: 87 10pt "Segoe UI Black"; - - - - ROI 1 - - - true - - - false - - - - - - - color: rgb(0, 255, 0); -background-color: rgb(20, 20, 20); -font: 87 10pt "Segoe UI Black"; - - - ROI 2 - - - false - - - - - - - color: rgb(0, 255, 255); -background-color: rgb(20, 20, 20); -font: 87 10pt "Segoe UI Black"; - - - ROI 3 - - - false - - - - - - - - - Create Composite Scatter Plot - - - - - - - Apply ROI Mask(s) - - - - - - - Clear Unchecked - - - - - - - Add Checked ROI(s) - - - - - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Image 1 (Blue Shade) - - - Qt::AlignCenter - - - - - - - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Image 2 (Green Shade) - - - Qt::AlignCenter - - - - - - - - - - - - - - - - - 0 - 0 - 1034 - 23 - - - - - File - - - - - - - Plot - - - - - - - - - - - - - Save Plot - - - - - Save Images - - - - - Swap Axes - - - - - - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + CorrelationPlot + + + + 0 + 0 + 1034 + 859 + + + + Correlation Plot + + + font:10pt "Segoe UI"; + + + + QRadioButton::indicator { + border: 3px solid rgb(150, 150,150); + width: 15px; + height: 15px; + border-radius: 10px; + background: rgb(255, 255, 255); +} +QRadioButton::indicator:hover { + border: 3px solid rgb(58, 66, 81); +} +QRadioButton::indicator:checked { + background: 3px solid rgb(225, 75, 225); + border: 3px solid rgb(255, 252, 255); +} + + + + + + ROI Mask + + + + + + + + color: rgb(255, 0, 0); +background-color: rgb(20, 20, 20); +font: 87 10pt "Segoe UI Black"; + + + + ROI 1 + + + true + + + false + + + + + + + color: rgb(0, 255, 0); +background-color: rgb(20, 20, 20); +font: 87 10pt "Segoe UI Black"; + + + ROI 2 + + + false + + + + + + + color: rgb(0, 255, 255); +background-color: rgb(20, 20, 20); +font: 87 10pt "Segoe UI Black"; + + + ROI 3 + + + false + + + + + + + + + Create Composite Scatter Plot + + + + + + + Apply ROI Mask(s) + + + + + + + Clear Unchecked + + + + + + + Add Checked ROI(s) + + + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Image 1 (Blue Shade) + + + Qt::AlignCenter + + + + + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Image 2 (Green Shade) + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + 0 + 0 + 1034 + 23 + + + + + File + + + + + + + Plot + + + + + + + + + + + + + Save Plot + + + + + Save Images + + + + + Swap Axes + + + + + + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/StackViewer.ui b/xmidas/uis/StackViewer.ui index 62f2665..1d58ea9 100644 --- a/xmidas/uis/StackViewer.ui +++ b/xmidas/uis/StackViewer.ui @@ -1,335 +1,335 @@ - - - MainWindow - - - - 0 - 0 - 931 - 815 - - - - MainWindow - - - - - - 11 - 30 - 701 - 326 - - - - - 0 - 0 - - - - - - - 10 - 390 - 891 - 371 - - - - - - - 720 - 80 - 181 - 171 - - - - - 0 - 0 - - - - Image ROI - - - - 11 - - - 11 - - - - - - - - - - - Y End - - - - - - - true - - - - - - - - - - - X Start - - - - - - - true - - - - - - - - - - - Y Start - - - - - - - true - - - - - - - - - - - X End - - - - - - - true - - - - - - - - - - - - - font: 75 10pt "MS Shell Dlg 2"; - - - Size - - - - - - - true - - - - - - - - - - - - - 720 - 250 - 181 - 139 - - - - - 0 - 0 - - - - Spectrum ROI - - - - - - Sync - - - - - - - - - Start - - - - - - - 10000 - - - 2 - - - - - - - - - - - End - - - - - - - 1 - - - 10000 - - - 2 - - - 10 - - - - - - - - - - - font: 75 10pt "MS Shell Dlg 2"; - - - Size - - - - - - - true - - - - - - - - - - - 730 - 10 - 171 - 70 - - - - - - - false - - - background-color: rgb(170, 255, 255); -font: 75 12pt "MS Shell Dlg 2"; - - - Play - - - - - - - Log View - - - - - - - Reset - - - - - - - - - - 0 - 0 - 931 - 26 - - - - - - - - ImageView - QGraphicsView -
pyqtgraph
-
- - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 931 + 815 + + + + MainWindow + + + + + + 11 + 30 + 701 + 326 + + + + + 0 + 0 + + + + + + + 10 + 390 + 891 + 371 + + + + + + + 720 + 80 + 181 + 171 + + + + + 0 + 0 + + + + Image ROI + + + + 11 + + + 11 + + + + + + + + + + + Y End + + + + + + + true + + + + + + + + + + + X Start + + + + + + + true + + + + + + + + + + + Y Start + + + + + + + true + + + + + + + + + + + X End + + + + + + + true + + + + + + + + + + + + + font: 75 10pt "MS Shell Dlg 2"; + + + Size + + + + + + + true + + + + + + + + + + + + + 720 + 250 + 181 + 139 + + + + + 0 + 0 + + + + Spectrum ROI + + + + + + Sync + + + + + + + + + Start + + + + + + + 10000 + + + 2 + + + + + + + + + + + End + + + + + + + 1 + + + 10000 + + + 2 + + + 10 + + + + + + + + + + + font: 75 10pt "MS Shell Dlg 2"; + + + Size + + + + + + + true + + + + + + + + + + + 730 + 10 + 171 + 70 + + + + + + + false + + + background-color: rgb(170, 255, 255); +font: 75 12pt "MS Shell Dlg 2"; + + + Play + + + + + + + Log View + + + + + + + Reset + + + + + + + + + + 0 + 0 + 931 + 26 + + + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+ + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/XANESViewer.ui b/xmidas/uis/XANESViewer.ui index 1209c09..aad2c61 100644 --- a/xmidas/uis/XANESViewer.ui +++ b/xmidas/uis/XANESViewer.ui @@ -1,431 +1,413 @@ - - - mainWindow - - - - 0 - 0 - 1089 - 854 - - - - XANES View - - - font: 10pt "Verdana"; - - - - - - QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -} - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - 0 - 0 - - - - Fit 2D - - - - - - - - - - - E_shift - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - -10.000000000000000 - - - 10.000000000000000 - - - 0.500000000000000 - - - - - - - Alpha: - - - - - - - 10.000000000000000 - - - 0.250000000000000 - - - 0.100000000000000 - - - - - - - - - - - Fitting Model - - - - - - - - NNLS - - - - - LASSO - - - - - RIDGE - - - - - - - - - - - - - - - - - - 0 - 0 - - - - font: 8pt "MS Shell Dlg 2"; -color: rgb(0, 85, 255); - - - Results Text - - - Qt::AlignCenter - - - - - - - - - - - - - - 0 - 0 - - - - ROI Position - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - Opan as RGBCMY Image - - - - - - - - 0 - 0 - - - - - - - XANES Maps - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Show Component Spectrum - - - - - - - - - - - Live ROI Fit - - - Qt::AlignCenter - - - - - - - - - - - - - - - 0 - 0 - - - - Choose References - - - - - - - - - - References - - - Qt::AlignCenter - - - - - - - - - - - - - - - - 0 - 0 - 1089 - 22 - - - - - Image - - - - - - - Spectrum - - - - - - - Fit - - - - - - - - - - - Export Fit Stats - - - - - Export Ref. Plot - - - - - Save Chem Map - - - - - Save R-factor Image - - - - - false - - - Export References - - - - - Export Fit Stats - - - - - Save Live Fit Data - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + mainWindow + + + + 0 + 0 + 983 + 811 + + + + XANES Fit View + + + font: 10pt "Segoe UI"; + + + + + QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +} + + + + 22 + + + 22 + + + 22 + + + 22 + + + + + + + + + + 0 + 0 + + + + ROI Position + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + + + + + + + XANES Maps + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Open Composite Chem Map Viewer + + + + + + + + + + + Live ROI Fit + + + Qt::AlignCenter + + + + + + + + + + + + + + + 0 + 0 + + + + Choose References + + + + + + + + + + References + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + + + + E_shift + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + -10.000000000000000 + + + 10.000000000000000 + + + 0.500000000000000 + + + + + + + Alpha: + + + + + + + 10.000000000000000 + + + 0.250000000000000 + + + 0.100000000000000 + + + + + + + + + + + Fitting Model + + + + + + + + NNLS + + + + + LASSO + + + + + RIDGE + + + + + + + + + + + + + 0 + 0 + + + + Fit 2D + + + + + + + + 0 + 0 + + + + font: 8pt "MS Shell Dlg 2"; +color: rgb(0, 85, 255); + + + Results Text + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + 983 + 23 + + + + + Image + + + + + + + Spectrum + + + + + + + Fit + + + + + + + + + + + Export Fit Stats + + + + + Export Ref. Plot + + + + + Save Chem Map + + + + + Save R-factor Image + + + + + false + + + Export References + + + + + Export Fit Stats + + + + + Save Live Fit Data + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/__init__.py b/xmidas/uis/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/xmidas/uis/align_test.ui b/xmidas/uis/align_test.ui index da5604d..a5ab854 100644 --- a/xmidas/uis/align_test.ui +++ b/xmidas/uis/align_test.ui @@ -1,71 +1,71 @@ - - - MainWindow - - - - 0 - 0 - 280 - 271 - - - - MainWindow - - - - - - 100 - 50 - 93 - 28 - - - - Load - - - - - - 100 - 100 - 93 - 28 - - - - Align - - - - - - 100 - 160 - 93 - 28 - - - - Save - - - - - - - 0 - 0 - 280 - 26 - - - - - - - - + + + MainWindow + + + + 0 + 0 + 280 + 271 + + + + MainWindow + + + + + + 100 + 50 + 93 + 28 + + + + Load + + + + + + 100 + 100 + 93 + 28 + + + + Align + + + + + + 100 + 160 + 93 + 28 + + + + Save + + + + + + + 0 + 0 + 280 + 26 + + + + + + + + diff --git a/xmidas/uis/animation2.gif b/xmidas/uis/animation2.gif deleted file mode 100644 index b5261ca9dd2596268ffff8554552153335797bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94900 zcmeFZcT`i2y6v5ikOW9VKzdC81v|Z?DKrrwSW!^vMG)y7BoL~AK~O*`0g+-sM0!yI zNEZ-6KtKZ`O+=c2sPQgj@Atgl-tReg-*dlz&K-M=IYL;h{NWmAjNhDVK99i>eRU1% zP0%K29kg}u$sZ6scI01?xam=;6Q;7KkMBKqTK>X0RhtWkogEC_FPr#yo(%Lm6C7Z1 zHTYuqRfm{J?_^r&opeg(oj(+wmGRGt$jYG7v!Zgcqkk**ZcajeUSh$$q@sf42Sq7> z9^MCZ^Wptlj~@J4sl^ZfP}<{1+e$Af&MtdeP*Z)szV5+qJ$zRGuc-L>v&W2vCyk8K zrpEFY%@r*#s@qy?JKF0zJDzoQHgvya^mI4&bho_jYwLgA`R2{bp~2p_!>`9i2i}bh zzJK@Tw+7$8-xBlv&^Yt$#Q5mshp~^7?>>HfKQ+aio*JK-o|v7PoSXglX>RJ%r|J3m zna_Ye&n+y>FD@=DEiEiBe_2^sTwPgOT?MrK_3O&lud8coU)R^aZmh3wY;0_9Zhrs% z12}$e9jyQF@$28mf9nq5esP)TojgGzko6P~$V0%u_W}0}3W5MX_icSSw!S7H1Pi3V zA!b-YXG9=&?6GVs$!UtlNE>AsKFNJ?O-Sj|Xxo#!t=F-nII$z8dF{z^C+=Iem)`44 zmACE4IPx_A#ThI{&^ULOTxISWV|V6 z$KDHFRZl*&(xi`O8CREn>`YT~dDm6_bh?L5x+Za~rfl|g;fV(qUek!l*GQ( zpc;xnPPavQ_4v@N!qv2?BN25PGBJ??EVJ|MqPyfGNS!({I1+{Clp{W%L$e9jQ*LgH z2$S!_;1tiv8Thb`rZI((KI>S2uKm1uksc@Elq7^vgB{Um&2J|$xogQTl5{9q>gKoo zWgj9A?dfk#<$q@;4!4sUiX`q|Tc04rm7E<9?cwK<4ds_xwZ4jzvWw1wRHRx;T%B;# zC8E~^a|n@GO}e|RstAakbE&X5=T=R%IL~d^q8@QBprDWtaIOR9!y4It-0rTJRgpb0 zsEV^yW;K9b1iFFc+wGey0F8bS)*H=%eaf2EBfheR@6DDt5X$+pMJyi?5Ib}0L&ahNhzoVwA_rksSWvZyE+b@s z4nps^mMPdRrUs?uw{Qv?W5Mi#-eFV;p0oH}_2RSNWh8hsmZQa-QV9COPW?G;HcAMq zm6z(aWa#se3;2&PiP@!{5_PJ49Fk5kIjeof(@8>t>IMl)(ETzam<%SvJzBTolp1a##s=rgiIqYh7=HUY>oBKhVEz1ehKad7DoLXrzEU9 zmn`VtK&9p1g?Ppd#@I}K=!e&)uM59oz1*>QGU~_C72&hOVYTnYyc-d$ zSO)T1kg;^w;4`I}X3URdl8pS0C@yNxRW%RjX0QCCE-OZO`GWzIA*(ti+G8c#84#4} zh{(d{`Ui=U#555iN2p+!Xki#sh73kKf#q0XVG+V@c$iY&I9k~_jW@Ned6(u=EIEF{ zt41bjFCPv;kZa^`mXFRo{{j!ge*ZsMaJUh$6z@(-je_)NxLu zw!0GR{*m%^M)K!+8${tp3{gKycKd;vBm~am1)a2uNX&|L329J9awhrn+5Ij(*RHNL?SDY`Md}} zLC!_qTN!&e1*pzP_c33jwaVBl#+Dj8-0w@Tj^DLAM1#-joYKwavTJ+G&mK7|5P9qU zyv%_wQbwNWz7&0ey{2$`N1b6`raP-y=D4y(k9KI^K}vrkg6@MIAWN2X2cjNYIo}5mnOcxo-?Fxbj4uQBD zK`9#k41}IsI8T*Tl(rL}vZFtPeILF*SD+lCG!iRa)*F$pIZ&hvg8bBkz|cZp@9Uoe zo7OD$7a#h{+142^tQj^?D)5!6YDPUXICpYi|5L>T8s+TtC9vwuB)98J7w#6TN3D6* z5bw-S7MB$f^-}|g3(3ZUY+8fJ#XLmR%e`V!ccCraDnXjNNp>suO^g^V)So2>ITPzq(hh98CpLtN`?lwLG6RGj&kZ(30@QryR<;0r1? z)57Zr@>W)v@b9;dqg&=adn|V`%t^WT2(i5HJi3$M6b_9DUtw>d2XAjd zGWBlqsM9E$9SwfM&QxCghWA2YxFW7s5Z<^TXtRGq1=HS8P?h{1<`~9~Rbaa+s~s-j zo{Dpnd*nQRHci2NjZM#xNeE;zd856gFVRclENW@`DTJn|M6=?fGrJV$I1bw*@_;&>P=+j7$)jpNy?#cAKFB zyO#@CT{GymHLT?>#fMk#-%AWk9I_s~?pCkAIqDS9 z87l3z=9O`!=li?wtn~)&XN%FMKPE(fZM2y_TTboyF{SnETkrK}s|BV%=PZ704)r`+ ztK8S~b0O&0cP97qjaF0E@||BlW=x-N4)(CtYJdG)y#D;hwCS(SkzcHh9z*S1uX|#lB@GEFZWY{eioryhQcl~!YMY&F<+_+!3`MkoHdaZNVSrlT&YS_QR=HUKrhnqqsRI~2*EoVMS?_{k6 z7kG~icpZ5DOe@GhNkEQe4#Gv-crWzJ!rs~zBxbG-UqhssN;XTxjy^HqE0az9^((|q z79IlS#DYb6KBijehIK8ykMK}WwS=<^#L!72A7yc*ur}~`#JY|FA(CByk`rEfxsWU= zNi;X`;iZ|=`B3(4nQ@43uX3*qw3}PUaDu~aB*WzSVxqNJXWn@!Y#K_ki=od>5`AR8 zVZ{gRnKBfGeRdyw)CKypZ~huHJCC&B^~Vt}8VOF}!)V{Ik^|{UQ^}m=?W1?tPVFwZ zy13I%HpE3jIgfbt$G2=hY4mMIoZKGo=veu!Wc5sUywJ8--8JT#B8fUb08TQ0Kuoj=3 zS8n4SWnyaC4~?2Nv)UC7-ZYT?>?7iVbzr=U zi&sBKv4-rDQ4FC2cg9r>!+)6h5rZBk`MnF0^d-Fy@pD%t=Nx#ZUK#lk9PoZOoIU#E zH9xT-_?_f23fUtp%kOd2l9~Ig$EGACJbsC6E;5MR!$ zZ`Anu>d@59a>}9kj@NuKD{k0#_HTR3US_(<7D3L7K?~D0yy-zEN}{H06t(eof_H{9 zWXCxxFaqDeCcZdkx`=_RJ+wx-(VHZM3?B>BEJn)5=O7&oNdpj0ut^)g4!|p{ zEB+DU!D(rSta2bchPk|{Y0YRWYiCpEUXc>~i#_L(;#|_2Qb5w|GKx#rE_&yln38|7 z_x7v!v#Xt=il7$Na0SQ}GN)v(dy8fk59;ULqr$tGEr%36*j(^N;w@IK#0wrsP!pH< zk2b?yHbruw@_90k+lW>EPm&U}v1T3W?MGnC2ZEdJq-Cx6h`ydT!XWqL49<3($mKnl zYI#poMT5_zrY|wOtW(N{Qn zswT(zuO1Z)7zWe#N5(UZZLKy-jve;h5oeN=EAzBUuPLJhsj&~jT{+RD34MarWj)L} zwBFtmy19}KLd~3+z{5oR!`S#2@mxl0JJi#xk@_iNBY5peU7Daiv_ibt<9ZPz)n>peg?U6kHh7>nQXl(jq>}K;R)`l&{c{a~64X_S2W*=?|btJPkZ&|8{9-umMs|_b@SAQFDk|E^?L_j3|Yg(hk$$Cz$S`V!LUi#iThO9ON2goO+Bm5g>7`oEa{3L>q_L%IE}5V0p?xU7t=P?hc| zJnJzGJxbYQarPO9wPMs0sop6m|$0x8W!Xgm`%rE z?hJP*krBc}Ki)|fe&X;nc}(HRjnaC57R4bfpFO5EGZ(DUE4cWG5M|}0#ErxGnwJHw zWb9SKi`1OxHoN!5vVEtE2>V>nfY;&Ar>T5_{ipSni3c7!n>fNdvJ<_Da;Q&xE{foS z)xY|@-po+HTGPleTb|BO{G|6?L#z53=m{#2luMGg1y&GO@9 zh%?G~9mCGO&Y)brq*_bY1gEalaS}(MsIs;j7VZ`NeCrNktNQ4-ie}T0jLjf{`G=;0O9|G0O0=zZSjBcLxBH*hXr>^OR~yJ0k+Sps>rXY z`ma?`TMcN7{Of9p>H+ev`y2T`|4shCA0IqzZrply(B1|-JNQF&ogIHE;DSHS15X)% zM+`6edfNtGcf9HE8hX<^@)l74yRmKTADx_hH}w&Ci11-{>j?tz_+WP7^EUH;URngS zu)MUmvb_9t_3QfD`fu_F;D75`!JpCpPw)RbIQ#F<{A+grz<%NX@z}3&oEQP)@w{9{ zo*#+ed?gVly1qcK5Bjy{thI4@tjVuYRU*jOGqWq?aO+v^z=c`yS58&*oMy4O&POb* zRP{D)Nu6LeYsY3%*!T6Fo3zSM3me>F-{nugltQ|{|6(II=y=_@`rOd*Eu>5r!S z=BbPG2iB~M6AeYCZ3&KVW2e1&bps32*t^@?a>F&NF9an?8D$gW-{wpOvNGp1W(hYZ ze2dcKZ`VjjUznSfDm=Z*E|>mz;oMB>&ccoi;3G8;@T_uY2C;L`N!i=Ehv3e4PQqGg z^e}A~Op4j@-d@j22|L-;y90!97(d3HfY2=DF_RS0)wpah_@>RE0{)W%7k4jN(iih} zjT7R7D$g%Ai8z6Is`rI<@D!<(cG4t0O2(I3u`5YwBHXDz+}X+6{STs_dn(VzzBtg_ zDvBLMdUi!bS}k{@&d?)BBWzWV4SvoEx{>^QU03>Cdnsu!ZG%UQDCAc&;zKqneMvvk zDn42g#KmXWYOEyQ`r6Duhr-YOmD2J?f}huL2=Z-Qf7m`dB#ni3E)ZuMAfkxycmAKw znTv-MT=pP)VG=7FBa>+U{Wye5)d%CjoHWTLlV>YEhsP-zuiby7r-|z2TT>LtR`cg%DnpgHCPJmC$^F zChW`QyXUX_ynz8R?vmP}1A^b%utuFge&8Eql4>#-Z0C&cqW3q*<}$~THu1JRJz;gV z{e%Sn{+y{eHiX9JlNgbdYp4~&woRU$aaVuXicW{?6Q?|!NEBPCU;eN<<-Vj#)I73` z#u1Iv%LgKw?L<%-e7e5J72K-*ITsyTHM`$fmDEJSR5=qMg)ec5x0`Kxjdl zJeuY8pNJ}2NGVKABAq^QAAGNLHTwdpXop>(kcP6)Ly=<`7zL`v;>@z2NGL=exu`vEkg7Ovr(>#!yoi z$glf@HR6ngu=1mk6B3i?D~1sML_GWchm6X!*~gJZN7BG(Tl27jjko1$LUW+-@lg&cqvBSPfT2NkdwsW{L;L9!SR{O z%z#QAeFSz>%wh1YdGCR=k_hxm@GhC=m+@%NV0NT4QP!X}<;@CVBf}Z{OBnDe-tGn3 z8T%#QzGmO8lEUBUOt>4Mo-)<&5H0lyyEhMgU*Fa99e#FKXXPOAwBgO^u1jz8)_ZGJ z+pY)cOmaV{>Ar8abek&}qlUG3&UFNmH+)NsmpMFyP-v>`dEiRO78!J5h1m}zxcbUA zb6>AmMrGN$9k8{)zSFPB)9ss>`() z4_boH4VqudjctOD%EK3rwlo36pFGEn#M5YPvDHS!jy1Z^Yx-#S1awmLxXr#}9<0JPl! zn%>Ipw;+D*%fhF{FQ0(|(9+VE?G6x716tXt0WJT%2DApG`J0;n&~F!je)oU2Ubf0W zKYjoa|M&0Tfj?~>zt3#_?N6+~{q|pD*MDF2*LMJ5`u+donf~i5Wqv54kk;b!N*e6> z>5z?ulMuMZhYRsqw2;bV?BrXYWz+B!>VE>TDH&h#ske(oji==HfKhvhe|{({KP zB{4+KKp(378cPLzOV4(y+bLYS!nH^&WJ7IrSF_!CP^AVG zzITK+A2GSY_aKxePyqrjPW55~P8#B{gD>)}Lm_QUTntW=FD90M(<2kQcaNMd>2Sja zi4@*z{=r9Mk2?M;mk-Dmj^sI>OJkR+7E9EdTF)XHg||%-L-9wfB?qm^_d;d&^-ISb z-lI%UWoLPeaoElc;)s%m@vYCz_q_5q*Gv1L6GK5vvg0&D|pza7H0nDoQ{- zD=yTAPP@zTlV?_3)mN69TUr#23Hp5A8H2m?j^gCU9d>Cqk@db@_8sB2eob`9IzI_4eBt?BCR40bViHV11VTDNUq>vDZ4&?^-AJn&yx>m2(3xDsKeyL zmF`NZH<2Cq2ijl^H=#DSOziAY=?c_mN=rBSi=~&f81%AA$BqUj4KuyR-$P$mu*ItAgQYk&cccvOhI%z-M#E;?OGY40^P5b(#q!- z!FNO|L}{{Bm;yhfZY?k5rVe-K)-x2ld4~1C`ihgAe;1*iC-CD=!70k@WiKq>%!9i+ z(Kf-#$l8H(zq}1TJ3a8ov9ZIK)DGybm%$euvl+@uEs~EFp_!j_5g)t0%>S^JJ^FYV zuKg-Wr(fr=gYN1((}v{X57~qRc31cobc8j*ilN=ENYi2XJ8rCp$k4KA}C z<%?k(zh@{SG-%eGR>-?T3`3X(k|=+?E>~nQO|QV;Bi=pUR`{nU4E1m+(WSpZIAsE^ z(7bfrgRe>8b1syVxkNjYhkq7-9KIj4oMhWi{5H=9*FCo!=Pu8Q#kRLAyp=z2qApKv zr(w!L-{q9m+G{wyfFqnjFOmx8^8^-4jZRDT-PEJl@558X!Maz678&`K4Ru97A$u1X>J%MwmWrWks^lNR-nY9%>%V_G%uF?>=|p8*gwAOP zv_biv;{)_$ZFipsN6h!wR#_c?vcTt~F5GxFTZF2n3*&Hmv|qS8;ZJw$vY>cQsz^#`(wE28=85+0bhly4In$exU#H_oKk{H;}GUU_{+Jn0>Zc5 zPWCm1On`xGUygPJkbzX;jf6qJ<4GJeG<+ILW;iq!6*x$@BA8+H+m} z71E45?4S@BuD>DYud-q-K60jlv%Lwa&7=s+`(pQPcu-9<_A98TGepf}fKHQxV)7~6 zxdThAs~=CnH$$tRX&*8a(~*D@CgjaE*i$_gyz!fNQjF>y7L2&7Xl2D#_FqacoozWweBGNU}w)K9Go@eNXh zx;(V6U=!-l!0&fHB!Wr~d}BYgiZ{%dJ9WLavl!+>)JM50-r2+kSE&S~28iQp&o#rH zeKWy3KkNI+4huO!Zj~K#h-@IdwXf+Gz9H@y5uZNls2`K1a_sZOzM+8$leXG&@h=hd z%{Ps1$C7cGR*`9xQM>MPVx7Cof!Y>Gw%a=4M%W^nd$}B1@bpr9+pUB#< z`lR!esfpn(%R^o8h&(8Ry1lmM`S9~3%9Vlp?&kX(bzxbuvrhebalkXt8(vmlEOFj5 z0aOmLOilG+2+7azg)}i*$Vr&;!dWFEx@0okSVxLt!yv(I~q{A zFJOt781k2BR?RT@YN4LpqtC^k#HCzi%SV0{C)FlvexW`#%-NeT)9h#0ee>DV4?4%a zwN0N%2hhMb?cG0i{@+FUe_a8RWc$w&eqIT2wF?$NSLa_8{*t4fn~Rah6;ofY(?R~0 zS3_*5;Xtd$E#aCM5DVQ*4obZRw0f@INe|D=h@xj-%exDN`G7OXC@lfN|88aZp9MI8 ze^7pPC6Lbpo&f0NJbGULm;tnLwyD3d>_rn$v#D%-@n==Fwfuvs+yCYa7(H9I0HFT% z0if*47zvTk} z6R_n2wmUq(ll-NvL4Xw?&j-ppD{Frn1X%yJvEAzVvlRg7ev9{i9{~OT{p~*+fGr#F z*Kqsqv;T*80B{4c|1Y=!0Vtwm%MI)-_2M$Ov5*rQt6{9dG{OFEoJ}Du^2J*Eo>@S# zr()4kR(!T*_GD|dSgq^yhz3iQVFiweQQ^BARB$MXM#o=eLvbE1it^y`l?fcwF(YUH z0&$j(iS(Jt>+631D8PdY1x{C9FCr8I5L{ ziL-M^y`rII=6ZXva)$~hykw>-F$kIPhTbR@wwcyAq0$1(b)n6wUP(f}`4~**RtY9v zsH3nq_83dh&}S!24j+j)pN74{!E?htdO&6TUJ^XES~4A>5HNLh<<#`YRL-PAOoA6n zP$K60WtwahCwrfxq}u7|=FAx!RL;v)z-}tX$4!uu8ZOnJ>BkhebIK<5IcBGFoUhFG zMW_rFM6aGVvboEG>)f}dvTAgwUF4QWKUIL+2!k&B!2Ff%q7lWMisf=Ba1LJ2ezPV*`M~L;?oUX*%HRq^;JKjw)(*ZaeB$&~Y|4l0q=Y>-{-Z~~ z^^kt$8BCcy=gF~gFkJlBqqWftpCvywTeK;9oK*^?0I2`2BzfO=Tb>aU1rF`uS3%vo zNcq&cMs70L)Kv1h{&G^ena_&D3}4%lRFj9My^L~pq)DjzRdojk%U=NtYCL^fVox0% zqI%*ip6!?l=;1l+csaP?N00dj4qUf@R`kslq!yL{lfhDu!E~R)<4t1Bk|>o120C~s zyZTQFf8`FCf-(W7=oBv0+yGQ~a?oz>Byr2MG#wc*l&=kN18F3Tj5RmwI8U=!H!V_1 zWGO~ZyGcwcHC1$ZF_yfVb0R9HdCxhs*iZrqPH)qb^97-dW+Q|j8tU)8ox>gE9L`?T zrY~_0i!{uE2z3}5$b^pv`%y@u>upW-JRiAzkTSfPdV*rhOEe=XP9FC5HYKKgLiFT^ zSHTnP`ijRbeJFM^BkJv0C;M(tR@p8ftB;^)pZg$Z_liuVGWg# zT--k{nv@%D+Pxfep+WjWZv(}0js~$ZjuX1-+^n?uBKBU2&56Q7qkXWJsJ!`HZi)~; zF26M1$Zq0dBPMD`J}qN^KSbQd5H6Cq2qgRk+S5mR0^jb~e~D!kjwFUrkW@O<7}O!s zkxs=b=OA6IE;>z;!;x0Z989DWqA)cXuZ%+oy>!w~#=zn{TaY_r3JsgA?#c}~ z3GoFAt1Nou$%hmgxbViLyj8w?%xc!o9rHTN1{R9M{%6~Jd5)8jCzFTKOJvIGPk`AF6t|zI~;T7^}Zye za?bo(d-4}mTih&;{8%Xz%pJ{-m&9l?kz%foBzwoy43)!fE-!lUzs zTqS=tp@v`HuwkBLzc`!;C6;EYT(Z6qH;9}m#|7hAO;g0uXtjPt+?BAsHh02=-bDH2 zmK-A@)bW<$DQ~2%S~YUsa*2>1eFMPij2q96zH&Eu31@pFJf`zJ8uLZk(7f3Hj66hD zT?Tb7VXqjsZB&UHmX5Y2`b~4?CuCUHZ9IBNdP93xV1iPx`s|Ddo5P57`8`u!jPYF3 zy0D=+iR!E0eU4i?IwTYXk(2}TBIYrN9x@wNejYGr>|`$O8L#F@Cy6vRP_gpk^wzO> z3Ssp8%RS9#bs+}AU0#jaMQ`5lS%kIXKYV+85a)uS+(^~7jsT@IrpB=16&7|2aT{27 zAqH@k&0K`qYz>%zR8i>ai0-Pust>K)@*wZQUmJw=bP#i5gX|(U2(MhHgbAVZGyElr+D)0>Xx>~$ zbkh3LsapDqErf+*Wj+7PM>~f;c=e+LxI_KM^?W`tk_r~IM%zd!V^t-y-kjrF5 zN$NwI8Z(f61UHrU*p()8@VhoASc&eFDfZ1obJ*~iYK7>SGvTmfi?6^kp&PqkDlLP| zJY9KNm+W*&lFBi0YduvQ#hB#VgebJEmw!D^({b1A$~lUz?}@(@QS$xmf(b%2=DhfY z*Bg)zE-~e)V|LVxapukgzAi&^!5;1|mZRbZ2yuMk`>(sA(aHjpc5$hNm|g}9KQ-kg zqw`*-c?hd=y-kvt@IFQsj@pH#aNKa8iEaygQ+}&WqLB`jk)w>P^BER7jx9d4nCxqK zQ#Pou?5$@IGA}w@lUtj(7WeS%t7mTC^-Jrf4|I-6i(V{E{Zh;$_9?A3`2RlP|L0Pitztge+=zD`W@{9`Th2AEjYT2fSk1jv zm^ zlh#+UQ9@=gba)9Z5@u5^8gz zGZJZ=Bq?KKC5R@9f|Ds+I#$vt2%l+je@n&HtOSnp2HC#$s+0F3&Pb%(bF-*OCnunf z3r(f@YAVZmNy?oR^Ws^Z#e~A%9N8jqM<>!LqoQ6~sm7)gXV0Q)- za*_$=I;IP4WnbNWDOrN$+x)6U%sogK=jWb{kATCRDPIke0a6Jjk3vb4ZP=rMI z1+`tt?(Rs1;+PPOXWeeL@+a-Tx^vRR@`{meocLkz%Ja*h(jg(9A!Kox*4PuJTG#nk zLcK&SQE-bUczN+v`>vm9#mCm?CQN+Dk3N^u_<6kFK#Ut+*PMJxX|;30WPBCU{5<~k z$(0RDVT9e@oMe`SS5W-0klSPA*JEMipHY2x5|%Ev?Vny%Z1un{2#ZL>qZaD4!4n7j zDsQPqA+eSi0#wi+uWahqyaW?s`+V7;+xO2^)1iHQXT)&f2m?zG_A@^H zOP*pcWL4p42}9vaz!%|0$HyUSj&&48D^+F5pBo=czx0a^U3vU$bL{)6 z7T6>(ga)&hvyT@3H2wUUaOkuTlzo}YYE@32J+GO7+&s!$YbHn6JtTx-;@^qH=fc^~ zxd)BaT-%TmVzh9uUll}-7tFKa(fXM4Is_}kK{;4mN-vd77iW61ipX9;j*zF);Sp?& zBD>tFES~@?G(??|dlH!L?qZ(LUu?u3x*ld&1VJUG>A)85Aw}FLR+P!l^G2`70Z$|@ zHmd1z4>5yvEOUfqrcyO%W?>EwB~EVMZ29qMDJUwvkuMmot;5^n5A^mSJ_c<>k!4zp zah}uzFPL-*6C%*i0LNQF>3Ia?F*!E$ZG4n6@_@1{2n^eY?>K&I8S=BUUE+3F#OWF! z=f{xtXehxjY0J0rM{k=nFLqpbb{v^q*CmXN?XM5S!0K1M6vYAgka z_LKCe)(l2gOLK!pa#4H8W+|daxl6d8qu1gPL)KY-MvdGu);N6d(wPZ1h*^mzG7RE-l{v5&;JzcjNv8Xd_zaRRLd>U%P@V> zTqw8Y*GHmqyA52K12y?fb|>|IEmR<>DV~ufT$o3S2Msn(I7!==Jg|8%OjDk}nc*s9 ztaI-hxJ7l6J#zm%mv-N&7jM5JFxI5A>BFWkO2(5UO2XiHGNjW)1)9E}_0Up@c&6v= z0K&!|vDq936~j`J&y67+Y`F&U`(@B{2n9J>ju9L>{e*tXOen@;NL)bXq-2|$@5+7J zbl|O8jAw*J(f2pQyfP_o2M!>n(rIr42Ok`4yX=q0U#ObE;|>~L2~@j-*;O}Kysn2q z&v6Yl3CJKf2Y_DujiFy}2Fgc-4^q^uPEy;jjM*2{_)#Xa=90RGQQG3EM|){nR_4#G zv|_xpGQtl;wceLvxl3b$Z9X6H$7X&mi9fG&p67%*PO#X+*WMB?5L6yBkn=d^+cgZX z;0#W~*FEgPhYzT*b*#yoV&WSsyNpxQAyTSe@FjOX?iFpWN9!-M+!1?ilrE}uj+{b7 zpG8^^B2yc}j~NOxe}Dz0Cpq^ex_MezUR2mIi3k?(4${(w?Ab8{VdyDm@F&nTA8ynm z1JOA}LADX|!rh@J2obU1mT9^4Dv4eTQroJ7vXa%wS`pksf#pZs6H(@S zef@hpARdQHKJ9AKkaD9jyx`w5FoFVbh6*Bk=W#+$l%|F~0*oMrMQ|RKX(PUSLY#AF zmmX?BT*?UzgPgRRr_flfej}KVx>NX7nljR$CeoV@lR(G7~UcOcP zlb>Mlo2B=h@s~37@j4age>NoOeArd%h;X|opeYmUvo+-kRNOPD*;&y!^te1=lr**a+{{#W2f8QmfyptuYrck zU#erEAE>pw*3YuzYLF$4^NB(Yj6MoQ>%bR0Rby< zwzahwXG=gk=6@^TvMscpnckXs{oCf8EwujA#OpFpmiZ$mUtQapeqGxDwDCt^zWMz( zVE-=5`~zSExcy%<_P@%D|Gw_8?!f;e#Tiu|C?cj8plC6GqT8%3lv< zrjSti;_sy26rBiAG!T`4`4dH_W3AcXYTHryOLjO!YzT41nd*UlD;+qv|5Z2p2XWWw zC~t0j&il|(y%eag>&aBGFGOetkS_|PtJtCL&-N%`OgiJ;oni@F$m&KFAa(7 z=%t11Jbh*y^lkU5Riv1X*n=SO)BjJ5wf^p$&L#w!Cd37DeS#^==3xphRrYr z6Ny;9g^A+7L>}Wg$x6fA`tBS{PQrD|O5Z#!VJAaWCGW=ji$$4Jh*u;|PREZAU}Xal zMbY9x@Xv<#ZjUJ7KW6@h@4${D7eh|oj_LLJKHx;l0Gr%*iV2gDDfFO`~oxBC=$nX6*0Zny;3GZx6U0!{pv8tD#JV+DNU6dNuA;o)7iy)ud%3*n;kPwD01Z zi0(#HCqudAp(Ebq`%jKT^Itpxvtuwof{We9^Gf_#;F)>-&V{%8bg2k7L&<=}R|eSF zeX0;JRM1tPT@zpS$UENC?t#aOqAucjz)9C9FQ_v4REC%+l7ZxY)M5ER>_x}uLljSA z>x+FCGnTA{z4s|GFkwtG>MJKkp}xzylZM?}xzAf746DlMGCUDxK=N~6ECLBTbtH)| zb@(#sxpoqldWq}S%6*1fR=Y@kVz{pczC&T<%|Y%j%Lk(=Ak`t^eaHB)ZngUnEE|t5 zR0X^4&!ox`uyh6Gy&*RT-@6EI@faKbylYims=s6mKaiRp2??T%4=AukQbcUaYZ4(50N1orXMAIr8H-X>aL%p#xnLeM<=teC;C?nsm9$qP@W zQhXL`c{O9RzQ0TJNcu7P@WX1LWd4;Fy{^!`Svx)iKzN@yde|{*yLNbIzkaj-3)tP+ zva6Q?MJdY@VDuFZ+**0G&W~%DPo1~e1lt;5nu`=nJl!R0kt!Vqf*R1VXIP8yL;X4E zV5XJLMi>NzXHw;i$2nBOAcqX^1$fMaZ_U68x8S%rRy$F~g^dzxv`CgoI|=Dxs4MlZ z_u%D*Fz(bD5|YZbo8nL6DKKwV(>6J1Km+ZlEF^MhgLem8LHJwENvc0^2fdiNBB#{h zcQN7d?hkX8W*VDRih1lw!Zu2MV`!ML^beg4dB%+G=wzK!hEb9_0L4UP_@wCiJDLF~jY==GO%o;vJIdt|H@yub;r7%AC zO!{I)Z-|^AtG1v_f6B2FFYO?9CEpiRVKd_S5S8P=pF8ljU97?A_>%+q+=A*Zx{=>@ zsF!@rHJ00HjmMseG2xTruis0Qb{5iH{hD1R62}L3lL9=NMAXdf zN-pm7CZC`^ha1&>?uok3lB_WA78@I+t5mLdCxenSL6v!@^^oHD5(@H14@8ajg#-eJ zZ$?#Civ$#h-gTkAI|Su+XR_@GY=Fv^Aa3g6Ug6ieLiYvU(B0wk|M zF1=w($L!M9sIk4vjlN$ux+K-idG1Xqi=X%SyMrXb`l1d{`c2Vu;l0?GM)?Hd0@Gn; zF5vjlw5P6@jcIu1+0HZCG1tDwvt^!^tq5nW5PE~1#2*XezI}e0_dv;(U`y|YRD0d? zs+$eppuDQSNAfS+H}id$UBA(3!~8L^sd0Z1$Q@n8L!XBjO}YsC$dO!nf-y$y%WVk$ zKn(T!!@23OGriT%mYyU%!W>c(>2BvyNFOVrsTzx>!qeIA+GdEMyV8f)35{h*4K(}1 z!&Mc#ykK}&jF04)BH~Wm=$K+UN&J?=n&iL=D88|rMtxXE~ zB+{;!RgLsb?9YKXFW{3qpYbG4`+s(Q-xjOYOW|+*|GNwiMCIa=|H+6e7z`#@oIU2| zaw5PN*nat6>vV9y+0YQdjTfUh`;&h z0Wh5klvseS`X7^||NLfqfE2*^XD_<|0g?Fg?w5doH8L-LFRKKy?(Vn4J->%Zw}b9~ zcm+c5KMM%Jw~H&=-4&qBKCr#q^6x?SHq4Jqe)#)-%Plwu>g~+w>8%x)Gc&+|D$sBT zSRE+0{Ih_SHUA)>2tN&M!u)$+{`Wfk)$vsH%KYQk*e{TADA<2?|e zZ|e`*-)h1CPSLl)9(W7<`nwtbyDYz5ng9FipRfGG2>cbv{`=g2+yQ{<3I7jda7)BxY;A{s+}hHITt1gz`4kABA&3W%?1^ zmzsf%n55ODne&y++Tud+cV13RM<$5ZkjLz5wH_7fYk3mb7y#b`pgj^m`-mYQ@_BYX z3H8QE;Y&kgzabug^l=jElBE49Lqw9aJWznyf4u5F5u1WwBUz?Y*(AbFH;}Ijv#Ky) zVKiABt}CLBB`P?m-z9<_+AerX={nOR5!P)$0mdWE&PT&PHam$Uc6h4*b9UmI`uzaO zG&n~(8MU)RKqAK1lTOP*6gk@Zs|*E521Y7Y-6IgzjyU>YUAG}$Mm8cqS&N)|>A+;Z>%gv)6ESbmQ2Ug&J-;D81qaK2!4nSK!3S(vk&*}I zT!nOnKkE+~$r36sDt!p)BstzAGV$`eNMMY?z9%quRd2Uzsh+F%-JTriH*Acf?kjCe z03g3v?x;2e$vq}wQ!M=LwrB}5M0@$AbZYPreD^wX33^|u`7`C0C!f39PIJS2R&VK#7n72s+*`#9l znl+9vrSo+3#B|mNq^50da$+eo^oz3Zod4mql25!>OkPixMQc~ts>LS;fp;t_*G?z> zV3~_KeMo#^QXgHXX&?BZm}*k^x>(!ZT=|#(y!xv{Ar9t?t{ZUeCAZ4g*)NtTi#m>= zP?OecT_w)uZCE0V*O115=dZLg1S;h9T_0N_jOl-;`iDv3e7;yVo_Tz@F3OuE_`leD@2IBp zcHNtnPy?YR^aK!)8hTYj5~@fC>C&5mbPy3qXrUU4G(qW71OY`UqV%p*5wQUxf)o)! zu`%ZtaGZH&KlALp&%4jN&OYa?b+1eo>Z}?6`rOy|y01&!?L&%qS&XR=YoGFje`F96 z$@3Q8Qq(b)8h)GFu})Q2kxN?qR8J}ItNQ+b1*d?w&W0+A%0GJog*)M zQ!e$C@FaKX7!X*~g3_r370z`@3msxj+%9A4Z_bekkIoX^reM!pz*!FYCq<3V3tyPw zK$v9^@vOzdnUxqWo!+?2loCh)98SMfL{RIPvr-(;Vj(~YGb0tscL(>W{*)FLcpxA! z`6FnA2Jj;7{vc7Nc?Pd~EW#j&men6Yy}LK!c@#@Q_u9dkjsoI^&YJ_tM}Zl?OGEAxD~U4is8MU$5vqDY2a-Jj`kELd4mZiO>J$XDG||PCiPXic}~^+i$&K(E>IfJAuKN2Hp+RSeRS07O$%{d zg(p(Q*)i2{#Z8N92FMemHPYiWzYw|TO$uw64|UO4@eS6?RN}M>geHmG#j5AT?Lj=` zW7fK_J*SOs*Pp~M-9{->NFCKRd0d3U+>G<}c6MU0VkU+lV<@nuST;HhlW}B3hlKsj zX!wxOyDB6TEWTnjxl}qarbC$|Uq#B|w)b99fT9H=ETp=LwQ#f3CbP1(<$eYgS9hB`% zSewY^z?se(doeVQrEO=~oHd9xkO2H;H&UMGmnX8`3v263r3+lCd5)Z$Mn)CIz{*fG zhwYI}37o0tS#CIVek}%b-sn6Bk*5a7Jh@MQk1FBrajCeTVgiawi;v62<*V{t`v~|( zlHD^CjULs$Z%PA%(}vHuHFwR0W*S1DT9IJi;e99*A_wt?J)hSq*kIZ(jD;{J)*apf z`XX~VlegG*MRoWJu+bCS`4(c=oJR&hG8e9r+!yZbA`+c~QWOkHY~3v&h1y4Xmzi$i zgKb8IC=|4o7(LZa>h^~C`R3C%df;HT2>h;sZ;WPvh_3qmc8p>QXV%&YXalfpFic{PB=W3suWeM#^O`fr!f%2 zk;n66=h{@@D!3ac0ak58-g|f>X1G&ijJ`(MM8thXELA`p|C|$T`^Hl7&J8br7m?AD zrCXzNR3F8LBbPa!e#tzawwlX z_>PAofX?3iZ%$4Bd|rC+9T4KlTKvA${^;pb0NDYN9boZ;aCx%4zCf1O|1!YO|ME+} zi|@Z+{UD5O$zS%5UJuOf2bZnCwEKO$|B3bF1K59@ z1J*zIJ-?V9`0@w9{@?ly0C&s(1HfGxMnl(J)Ei>T4}kj^Ajd;rwBonHwv^!;fO9<` zRrl-w-0yGNUl&|y{qYC5Z_PwIQzy#ogZqb}1O{q}9=e+haRF=>0q44Oz$lPj1-9-A zbOAO$aF$(RY;fp~61*%0D z1t05D%x~JAVTyg7J{RmT?nQCl?_@_V@xa60vs`2z@A)e6l0fi_ zsJ&Sv&h6^SiO(yES^2G3^HVDfuH}mLiAz+`SE*KB*{*V_B#4krGXsapI*V#WYYbr2 zA_pFJ=v7CC`h+GL?P{kc+VR6z?$Sdds9HdlE!RK?X_OCb%fJxa3NL}i2^3L)j?5uWiptUJ z14N&JaCTuzk72c+10pHs8Ffk2SiN2j5*>~&6u4vEKtcCNs5mEE@opiW^mwDeViq8q z2J!U}BNA1@kqReBiN~?2yOnU|!08Ou%4y-bC2)5lUL2v#c7CRW=R$LW{^A=Z*OXGG zQ74Y$z#Z$++cb{$;{8X{I>c>ygaxSaIjB#jX~)@<5U?thR`?dl)zHm^t7YH_Mn3JL zlmX&!i#FyZC@qIWm_OM?*Qna`Qg~vQFqXbsUdWxTVyc99hf3S3t3K)E^RmNj%P=8F zw!{;rLSlt*IM>y|~oF#sN93)!;uqTp3*|{sz4h=t7s?YC$(!IwGK$ zKaon%5=#!RI^IAs8K6U6Q+nY=7mfiHCRs3qo@QbpB$CpH5nOv^;3b!B8HvCldGCc+&$;&Y?{w>tw=fGurcm4&I0V;= z@PD?{hoCKW)8k8;eSAeAK2i(Psm?wTvls!3=B1Ab>K~3v*v7Z2TP`_gCz-L`m_i=j ztP8t%CtWNh?4C#DfMBHLM3saG%Mu@OXzE1ZyO(oIoOClx`#S5=%O@?zW@@oath6X= zEjlaGTNf<*DUjHcdRioo*~p*3SHOZ)3r9ssT7y{5t7Ot6W9U5$UFD^4y`NKMTE6?+ z!-R7>K`mmb-jM_yuAWZS`@YN~1vl7tRa*ij=@{cR-LCM*GU$i;vCNgnh>5}Ofm*08 zS4kmbi7LYndr$5)My$EBMouHt;2%ke=Sk?ImKM{fH0Ugo9h?`(7Ff|J!j`e1(T`vn zjF7%|mIRff&F^zRO1uCmeEaft?x>&WcB!|aXN*+W*xjA8Aq69zUwPanu~q|R@9(`? zce17NFrlmVzCk(~-D*BKPgQ|8gmRD0(kL&vt+13g@n4x_+z%2)nVy83kTHKe;Xz!;O5r zx6=d*1S=Y!S1tRrNvAvvJvFL6!$gn0C;0vS%V3+i0Y74=X{vubjm!LN?e(DVbMtJt z=g6TnJw$tOkxM5=ydQdEq`mOEL9 z(ngl55tHdMUv(%_-)Tv<3u~|hk6uelFCA7}v3N3&_2$f*8>evt7BY=bj>v!D<~_H@ z%_^68BIUmV-2ZyVdO_#!j5TcNBUYh7Zt*eh39;ndEa*;MNon z+*5(z$jS;JBNAwnBInH9Y5=ZGfjd&bSaYkp3oy#Gj|}yWj{#PgzubTRI{WmahWRf>M9uJWGZfB^Go*15a zpX8PINge?-MEwKFJAoft0wgcAPx2S`NnQ+3_$a5?l_;zz@@JCgL&e7aBzdcUAbIWr zI>$ege8~0rI6C)AK!<-i_a??jRJS{kN<5F0_pKNp`O{SJ*FpXa=p`*N2^{7sWxUuz zB0`kI3&R2BK_@>;A3Jc|p4_C z5;xEp5|!&(!9!ernAq%a87<2VF-k1i5|6pX<&F0?3-_l4(O7rj=~Qq%aiDL5gX7Jr z7^ga{u$Kv6^sV*!89JKawGaKUnWrZ06LGEMRjOZG#{x|al4l@-{omz4cFM75{Q5k4 z){-8{EK(LL!TRtcxG+QfJ*INTf^L4T6uAjT@A|5y1P7Dt$FS{YC`@2Zf`ZmItPFGq z?uiP(Xl!5ctd6s}5Hik6yr%?ljwu7tdrH!Dkx5<@Ao-XR7F^Ix{vt3@pK|vZk>$;H z1&{Fb&Vu&m=jQYA)Rpqi_E1B$ag&cCAxo5Wq+S=JXZ6E!Qb1I)WPB&4%Udo0DDHSq(1K^=`Jhs#T^MKjz(T|PF~ z$wW@12_PmMWAcdGLje-J`!Wab#wJm3t@u<{Jtb1zl*nsBFoQeOirBIyhC1~)`Z(dm zB3R?OcG*q20(#|zSmVMwyI@b~uu||iR*#g|%uV4iuEq~({HP(wn*m+Tx$@*Nx7QDn zy81Es^Ox`(Wu{Dny7y+}(j7-!O+O6`NPhhw1EfsCURZOeT5Tl--%Dnus2YUDvmSQ) zzQ{VB3lq(rh%+s4;k5LQm--4Kl0;n0QwH=7fpy|8MV2smi*q_=kLM7dIzcq8W1xO- zVh?FLgRkp9^s)VzPnO^(sp6P{qeh-xG~F2r4jF4o27NdcM`K6a@iZMq7a#j8^1 z8$F0<n9jXsqQ~Ar!fOD2(R&2 zUlyn+w|^LYlHVmk6m?lRbf(J*wh&j{$XRje^6C*@7h0akBI-(MJU@CXfxmLDNy&Pz z%Y$_bxc}sm16_Cro|q-^g;O+jtfQBe2{BK2PJ$E$B(Znq%xi2Z<@ChgfqgoP=WN61 z1gg3M?$$`g`xmz;oRD%R@=UOhvRtN{HcVv~X4eHiUxa)h_^L$Mbb4vgT<=BGVu}bK z%|dBbnq(Z*%5>%0kyAL!_K7We*M zyovKF7Yp(v9X{z~WL#YIO#9Fg*b@W3=ahnOj6&8QlCxzvZx-TXYuKrHXki2} z5}y`Qpg=%S4)kBhsr7PNoO6L8^{uxuGD*;N%+uxDYL^}=wbD2-o5w-XYAk2 zO3=FLtS2%q&??G4WugCqx?*VpvMW@6bVyGxd;Ok^jBr`|nb41+dVFu0bMwX|tv;gH z>fb*3w5z6__AvrP<;^ciGkz*NFp%d3rIAqCq@>jVm&C(oZxxHjm6i39O!*01VvGH! z?gyVeGv;M3=r&z>M+tM^WCisszRObdI;H@<3Wc{Jj?dg8`chNL_kTsGd3|zg@AYuM z)H)KNY$St1x=$5$mDS zmO;aQy0oT|*OeD>^T>{t$4VLVkg)Jor~i5h{#*?W%|AE9^XX{=VeY_qB*Yzj`Sa)5 zwAA1XAmRP;#Vh$h5DaiE3S_|mln!{`_uHF*tVkdbvZ0uq+kO|wVXyD(Z0aR9wgM^Y zz>z25Uk7TFfP2nAUveItYyR6$vZ6jmmd(kaJ`cFn0iQbAq`q%YC;M6e8b3&BIXLP3 z7oP_iF8}s(^&g=90NvlO@6$V3OW&t=AiDi`otzx|0@&I2=hwX`V0j@I!2)1Umdf|% zKqn__hy?-Bf^?4(i|09b(Gb7{m^B zw)29#Xg$3R9s&&U;O+~dsJgZTLwx9_y(jld>+m1koL5Wph)+#aAC2cqGMrDGY0 z34nNzxn0gM4&e6Y&QLwQC+GuimxYoQ_%#!L@Jl3MonZj1GmKD-wN}VG{kNTD>&zG! z2xdozK*Ry-j9}KT7FM9F(k0{&kR21p4M^_H%Ao@;@oR^BN(o107M)0xb$~VgP++}D z3il(x8gB@mL5J{V=|uz988Wg5_Afs%-`HiZ<9%n>GGFY#ex#ZeNig%=z}0o{ET z8S6zsJwZ!Xv3E#40$SFpZ$QcmPU3OQIb>@Ky*_fz2bvUBu~Ln`Eq@&k1jr=VW1z7S zYiJPLzWA!SaxGFWiY8~|dzqs* zp*KgqxTHBc|h)5!Q!ds_HMACYzItY$?1*qG&U?;cek ztrw+>`OJw=udo+Vm%HQirJEng|JD=siUKTc&GSX(YS6eQKjd7AgH#1Ib@tmEfyRnw z1v=?n*gk0#l`m!R`B2BO( zH|>LOS0-sMut()F_sed4Jy9=gK=JO%O7yL{)YseL*^JTepK@^TUcEJ`8R8rs&5U5q zXGZv=D5;qYNuA3_$0@qmIPuJtvJQW;&eRQRF}rNyS$fa}C@zr)S42r04y40x7lCm_ z@l1r+7^Gn_gRWOs>LvxXL`pG(9P}oa)glN=DrUygQGRY-3h#8$GxE;PI0gotakv ziTSMm%%vHAml%Y490)u*ksB+Fzkc|PD zwq#pWZa9oxDkUh-JRIxhSoFxNbwt!_FZ`|2rKh>#Q@3(qb7JxeRilnZoq0)#81>)0 zkMr%8K|q#ETCmA@Ce(Je=QDjCq1ofo%UW`R&6UzYgP9J2Q#%0TK%91JdFpL+12-SHUPXZ2{PlRA+l<{4 z=sWUmq=QiaL*2}iozJ&IaDXMpCEUi}eEnR)d71km1aSw6MBQ+He~$I0%yeCjL$ys# zo_RNEp4zWA8mx&RjLC~I3y$sGaMq7tZH=0)Q-`mfoG!ir z;4Xg4bMp~YaYS_ctivigV$QMcYrVQyuB?5zBuQW{tnEmL-^KOW8Q1ia9Xu!rOxF3i zH=;NQV_nVmawavM>rN{e?;F=7`4q3I(k-?ulcQi#;mj^10$++PVSA-ncsbyxfj&wx3F07F23ZL?lwC*0Hnj;SRgpTdm%>50iZLlV+<)r1arGEq-YnaTNTvMjYf1}w5J%0 z+sH<{RtVxAo(&c|=A?WZz2NWi*=$QK`s&SV^nsqJ)jXt|S71nz{R@a*1md(&;1$-1 zID=Q8rYh_?!Z+Ry2v>u{f)pY$(!*S-~Yu~sltEutn{?z-^8-ei5-HV zHJLF-nX$+IBg3kL;XR=b^sow>8_PM`83djMN|oFb$dWh!-hOE*{-o4^^t7OiOTn3> zg9*t>KbX)gz^9%Oo}GCvC+mD(_Qis{m}@}s>NTKsDYc?(zgM*?yS4^sRQf%r@ea_C z3WP8M=5-*h1;BE^whkE6yG9=kPE7&?{__O%Cu9FS1U)zh{dYeBuletG^QAwl=zxBH zAe--7&&dr*``xJIeD$Bo`A^Lp2wFdo&G$Q#4y-I&2N1q*HzxwuYR7PwZ6}Y?9Lv<0Yq2-uK5wS)d$ht={=u1^~?I1I61tWm|qk&yo!== zVr7kP)sDS_=dgnIg(yiip8_+h$MVK!Y9iNSGn7UCQTU5gMvu8y80AebYOFH}UWVtI(p!v@MrDZ*kIQ6rW^y?CzhE-o zxZaJ?AS?@GXac>97@n*^m!kNEwvn;20^2e993CytS5d7avc9fmw_r1u*Sk}7 zs(|)GC5a`TN7XSY>1r;J#9jsU1}TQVjrp0x-X3NGzhV`=94Hv=v!eBT58NwO5NDibggSr;y>fq^SPEOrM0fzPpm6^#CqMNO!K)NG}pPY1$w4&yw9ZGxYa42 zN3ExWKqvcrj2g!M1?#)b1~YQ$*!NjTNt87h-rXL`IqhKM&zFU#3Ur!(+fHSdwDA-p_Bjf z^x=U5lJK!L{tSVmG>=HkoIn!$Li|qs?t;qL5e@+%uez3pu~i?wEGRDRAbR ze*R#S@@_}fTk%Ka1J-TNuTL_y!u%)&lH-c0Hf>Lxn1uA+a=^ciKRl{AewbLtE|91D z+sExo4Us>FT{pG~pDkYP&9Dq#lI@yNt6jeUdZSwrzwr80?j*~yU|OWRy1!sw;6lt^ zahkky$Eo$(VeFdW@2RG+PSaMjO;;k(VS>at_bI2CPu{+Mnw`}13@7oN1AdCmn|NlI zA}Y?F4)CLzL5Qto5#v2HEe}WTK@$6B!XBE*bo{W3VkzI7T$Kl;PO$HY zE3YpaTt4TK7G2;<{6t0barpzuHkPuKm_bcXvz6|V6}`FU0wl2qCWoSuI1z*T3bvF$ zV^W!Tr)3sn@KzShoV|l1!oakvLp%+4NbFEF%<9fWhHP}Xlqda=ooz-IPVSA2V3k3u z#Y9fRh?}Xh+6X#|P1d^pkbKiFL7Tapw&9E@H;}}xVWf~8xm^mnt=6aY$vyRcq^tRY z_^>*-BB@x@4fX(j8O4y9tF)&l>{1^M5*A9oX79bgUaG2%n-C|CbbW#;geSmd}wtCK1L=_$%QA+=MD?}&B(j<%@cvV^Fw z&{*C4O3X6=Gm7l^m_E*7*G&njN}0jAMHfw1E}=wo!dESoKERcx9#<-%-x(=apS&jD zRh2Y9cPiQEM1faHy}^++7ny*V-i7Tt#ZDTx3qu{96* z?CX#3h~cyTOq)lcFE)aT-3X*)9?;ekw;r--YNZp zPhpg@jWOxEhF(64sq}XRUCd2zfd}GO6QD!DiFvk$UZzIMDVHN9?Pj6xAM*vgk^fdS zOw;gTFT3qDi2uCA$a~KE!jVXKNQv{qosbU~qAA{fewT;vEDFj?cPG)j&bvP(!j=22 zAZCeSk(N*wgpmpG`YO|);ox*Tq{^Oj0@pgidXz3x@<=QiTGN`J=2Ud-0uBRV1=f?r45t3sIJktAn9Q)I?m(}R+jc-vDC zhq@mHYE4CYozhL{$oIITRiAp=mpyi(%qTb@c4XOGn1=b$CrUmpDq6E+=hTeWf|%N& zXJt{b&+I;h1;cA&)?{K|J>qJ{D9XK4GM`uf!mQ8;p*rh1dIg!L`Y9w@+Lb@J6S?To z)bf&rjTrwbeBs9YM^XsFFD~}`r0}tULxy#8$+B-*u^U>$gNtiKdjjJL(STri2PXtP z((TA4J+XJSPUe?rAt%9vQf?}5`_s*c{OE3Y4+?x%BN$QR(Ny!`Dy#8p^(nVkdXI%K z?A+E6StAIvJ(Ft;Rk(X$BP3Zd|3g%I&ilgmuqv0j%S`{D+Liuo_Tsm_&mAmL+J9$p zfk=vT>R`C^wf}d9c5-3rK>xj=L2?J`{^cs5;R81KgD{%m z>1n`G19)iwq5hYP2B7_4oKHsm)z!bw-(8skho-+um&jr3pt@VG5s0pfK=X(zEA>3Ak?Fq*^a_FA-f-Cks<*X{1WjL0iDq_@(m9BUk+7v zGCANDubpA_##7=s91%Jp+TyuRwELw?_teU?<2d(BUO;&K^x_g&HkP35B33h6Aqr)c zhzm@X<1U;KZPwD1$4@JrXunJ2aLG87NZ%!q32`@q3MzOpMqg%%GLyr@gdb`w3YcBg zj}DY#jydT&pC3Fj?oI$GzxX0jNU+}-L9dou0_EziUgc>4k^iAHWE3{3O zW1IvwxIj`oRWYfAetc{hq~I48SG-%CK#F)->^+u|!)D7*S749?$mbWlN#%Q~E+*G5 z6nI38g{*ofjFrRSs*=_55fRYJkwn|E0>Y5`X_OC-kB`<3`L{k=C}p3wjh*P$3N|2U zrhuN|%~)ma@btLW)$<28s2t*jcqfgqiqjeAO|CUIT`?&&9XJlnad^~0;(Ir4@(e>* za>^Gl{O%t^uL0Kr!B|fq7UFuBh|4FWeWaV?XzC>gXl!TvnWC`xZn} zIk>ll!DD2+kLJ@e6R4SaZ74_(@hN87|F>v8Y^*rt4X`%His>F=ftlFJA|Sii`{?w| z+BeQ;yK8rtP#QQQS40BG04H8yF%}dDi)wbZ5U*=y4PtrIPn+jy6yzKcUbhRy1(-xE zi3{I}edkZj&#`jzrnJ8caR%Ks?eL8Tw{hBauHVIFWb^#=@mFp%AAIb_RJVp?Uu`iJ zuP)LqjeV2);VNPGYU{Sj%Gi&oJIAuMcNUT_n9shoo9l6l5Ruu7Q*#eEI?By=>M8qz z0{*_AS=^7pI(e6R!S%*4_G7`a)RV(0@cN3@?Kd=G<=@aWYNfQo^_RQ?UndIE!P>cd z{1jc9yV#>pG2Guf=pJ?_vK}Q-GFl_3)&0A`KYD@ErG3;dfhG*_$c8&JT2^*(r3`Q$ zHpJ5_RZ_B7pyGPOg&2Rk2>CP`_aZ&vrCqe=%rdT?!KJXKSK>tdv>D8- zF&sJ3CPM1l#n9ABoS4CQ65$YwU40ij(J59;oK^g;iS}U~TL#PLU5bQC0$?ke$iA~p zpSn!1@2PHX8LPu=>0f2!;L}t<%&I_3dwXuxj~DV>E|+_(ZvwPIJ#?C{G&$7O=^w~g zA-_|0nH6VR>NBOe)>Ca&ZV0m)vMd$Ci#at(SP9;WYsBo3RK9rI#Z$ZLH=l2nP%?jW(>psk zWYay+S*r)1!!%R8{0eRqPC(s^6242?aa8g$4e7Gr&4~Lt9oEJfP#5)(*Cb&#r(*I@ z3gX6GtSGdY-g2NTnm2=93f_EwVV&+A?959|oGS7zo%ac2^EsB7&yBR+G%#Q>lVSRd z@JBR6@vhxeYi_=8VogMN8M;X=OCZ%9{UaLU=-%(`wjp)sgp%wRpK-bM=ts%$8GYwt zIQFG&OL~B-(r^*Q-os*}EW0PL*c2xl^Z7pQAa!2ynq};w%ELED0xuM-d5Bq`LnvHT zNUiZE8P2ub^Jiw^`I_<^VSaK<`;H7IFw#qr&&8_W{Wceykf+{c^CMc*&0_W+e1ZzI zqxAH*uhTn_%q+gPq|jZ;`Ih2j46{L&^nJ{~8{{MQT?=_fv=IaU;G=j(jOBXTRxRO# z>k(d>;qbJbpylD^Q(|rdBcY*MOb*_@Z56$K9!*-`JI+X z8e3H8rfzY4)WmuNGY35zpGBCk$^INP`@`K#IQIF-Q56hF@#?#guiYl7P;5Q_nX`h6 zPztdRb(xbMFl^^6yX@WGQI?Fvl-0X|?OL(PH57p?avDN;xiM*neiUdw31j>kuh()Y zFgbYj^ThmMH|GuiIO*>#$m;mp&HQFb=tWAFbEG@kpb2l|n22{ghqddy(?U{33P+B= zZ`H0o`nD$dh_LI`TX~~?>4!(ZET6ea*PF=nx%ninFXn#3i~qVJ)!$M7>IX{7e{O@J zmyx7Ds!o4Q<3Br$XiW&_D2FbV$54;wgg&6{^BNiO86W509|04?e>!pwwm>VZS$N3F z$a8iVBOUQkEn1DK+4R^{d}1P^2zE7!1>GTKxHbqFZG|!zN6-E zZ&Cm5Miuz^e%8$Im#X_|lD{I_0k`}<>i;ZY{pEx|FuDJ0{QkRN{ok3o|Nr@K1K9rm zfJ4-AMJ?OgbQyq*FDnVi_&|kH9#?C~UBo1>c2SEEo#XPWW90`jz8;$xhu0j(i>iiK1ljLSIyj1V zTsNFJ?S1?iXkevFA>W_q)O9~5vRc(Pnuc8pm5&Bj86`mz9B3e zS}Y}^MPp-gxc2-DIbCITkrniz7|2(@Z{xr+QRV_`Xon{n999ep>2or?mRs! zWs=Vmu2ss3WbsEoN75WBp?mjLqO?rFO(LX{CrhlZm4QaAw3bUzGNg$M_qr@nrzWX9 zfGc?{U~0T*44N7Bu9ipcJGVIIftpGICZRErAoP-%!>6EKLea~w=dj`m=NEFds-H|2 z7}`i_`V7;pqZSK<*`&upl{hGSx!iG6L{``PZKmzmp=ep0y$BM^AX+rm#lkHytqoCp zawdq)868}o)({xHN}|`W&}J0n@ZBCa7SV}+X!uMD*Tit*na~v6*Ut4WEYBi4gGKim_kp#UM8D$(yITXH_0TextWE4Ap!22^(Y7 zLGhrZi6Y{11K|f%Ji81BG&EO>nj&57ex68Ir1acNk1^<-r-Q~~ok4|6ipzOe&EhDR z*SyAT^Q!BeHx#{=37i=eGzgJ-`swaLcDFGCRg)9lo+ZM$GhVNUT%n|ASi@9>iDZ}D;^8pMO8+9Ve5Kp&XsHz%=LSrNnoG^@<9ev8J>8a9AT}YbOy>bBC zy9W=kP4D&{{UnzbWlGJlp*Ns`LE7kkz%YCt&^uPq!FZm#o0UO-P)XQ@C~mkB9 zEaAHcoTZ9GJ3BM=()Mxw%X$43Qk{7Z_S`zzXeN8zxEfPDQ-+Wmc?K6AvC5s`qvq&G z_0~F3GM=RA;t*)ZiW6Y)=<6gB_7RG}XyQ*W z1lV3uk02JO+DQ>QWPa7L2;@p}f~OfhSu(MGTEkh(IA?Z}>$Zh_LOsFsy87gmr821D zLkVM>%iJiFof@NXxKVY{<3?tRizMe*i%x^bF$-mk9@4Ay1c^L_o~&AP_oWk>(37=} z9rWo)YR3l>xPq~wo9>qBuFP`Q)tJs|^}{#=*5N5eO-ocl^J&-QB>h4JzR{*>%|%2m z?jesQ6PljNE6<0385p^(t-U8r?juc<`)4545Sl9PNu8D*}DYhh+%b;Lg)(CSdtaBDLlu-Alc56`4XAn1VAdilykK zq6r=Fx;iYWXs-DaiyYxcHD}K(31t}GmHnFTyqw{jlUfh`_Ty?7_HH_Evrc-8IRacy z$`Mr%-bJxQjJ7eH)9(37#M&2wOT861I=~X z0&j<6NbHfo&X}WskZyTZQ^i=ej>@BZ) z6SuRPD1skIN4}+Lh#i$%Bd%(?3^|CaW4Q#sT>%PJN9jsV zv@iE~_B_g&1`1U(f)sCF*n&66XE&vMdGyyq)b@8LN54+D?d{b6&h~qsnn&9=!bZQn z+?Vn1ZzdRjU*P?1cfk1m);}v$jnbi_{F{(?W_~_at)r~knE%wU>*8Prx`HMrgv?EZ zElhzCu`oSkX?8H8R_1><;wLQtCy=_k3-*jR;0H1d1Ga()pt#97mK;Ctm6+g@oCp|$ zfNEACr4mTGiMd)xEGbScF9R}e4u(`&L8`p|`vBj6&Gq!k>sd9`xplSIZr=iyfr{3a z%C=UZniY5e09Jz9`}c2>w}MXK!nUc0`~a{YNDsUL3?2A{fCqpB^&dF9J?LKr4r_s= zc_3qczeyEHlm|Tei?81NOoazl0^k}JaMA-F`mLRvgR9q{M)}`kWqzK#{_dXtWuyP+ zr?9`D&hC5e$=Ngq88kmTntsok`DL~LCD#8P?*Dfe;{R8#1i&(Y{qHaIk1Oc_R@lbA z5L0>Cz*SLKJ8>TF){tQ|uneTlbp~*Jt^$?;>EUxc0Rqo~Wq?>Itoz3@;3YbkE^>0_ z=Q7}v^}I4bcx2b)l-KgBi*HiNo}O4DnYj z{xYx&ECYGtz%n2a@Pd^uboXEx;1qYX?hergmH{{f&ngUu1RN{_MUXvd4($|ER2g|0 zm?19%;XjuFC14p~dZP4e8IZsg<$$>Y3c3DR20ZG$i#eX;{o^t)dvfvG*ZpN6?qC`4 zVh5H16V}W+U>R`vV;QI#UtvfJ4f}^>K$g4=DF4f4Ky%38G)B>A$n$owHIHWl-M*&> zDYtU{d82dpHN4QirzcqaiZsozuXu6a(;7_7l1>38GcZ6_%O!1P2~$6)!;9_KNsRUO zbsDE(QxkCp!PAttb=X|*w2j}xzI3{YbwWAEFpA%RVp+L)MRk}_{Zc{f?r4jjdjPFQ zmNt_(jr8`Ixkz^Wh~97tt_cF{0h7G$cCYV1OdGn!tXY06I9rLVD~;%t4U*ui^2>6% z2-pmv10oj?GVNblf7AIyq;D8|kXK~yUW1}udYLXU*KUr{Af>>B2i?yf#zcJV`}v9P z)Y=%Y$9H}&UN#d zZ=6Zhq(B%;(nGC)4aejc?CkgZ*9pAIRn9aY9I>^N9E073Kp&$T%aIA)+`3HO!js3g zcP_QxYag-OjE&v3hCyt&N3UJ$?$&637{y>?I354`t!FMf_Zp)uQ|`$;>4DKug)bNi zn*^Z2Q#R~nTOw}{jC##M2jX!4#YX6a#^a|QgnaK9&V{#1_dfJM5^p)OKxep>h|Y{B zPz2U|C(z2e8-p@Yi@^*^E#gKQyGP9k2>m~o0gF_gspxoAEG6XlA_J_|AH?lg%;=yu zA~ZsZ6AAy2?6mE|HBwJkb(@kwZaJFg$$*Yh9|eOi)R4Os-lsktkYaQW!}DxF_vrm` z*)z=qep>JTZEE%+PZ!=W9(;$c;qD-WFfaW*uHwJa^K$-qQz{_QtnIfwPl_Ik3$?8_uxm4NNdJ|!bK?i4 zzp-!?{$U1oUt(j-*mE6Xzq)c9uBnAeuJjcDVAEH5+xgn{ZBIVdTRoyj<&x~?sbF;` zu|oD*CHf=eWnerlV!IRpO6HwF#miXCCpph}oXCPwb6Pl2NEG4OY!BtD#LhEV4pbS1 z%z|_`V<8goXp;o>DTbc8Q&(-U@X(>D!U+yB6r$Q9ktgQ5V;@A+x7sS`-DE+%bF~wH zHNy?{Joz&_H!Lep7&Stt?=a7uNk50R8n3pbS`cCK^Q~bZF>ou*W!2gGRu~zPp43}) zT)3o9?KGM{TJXH6-uFb8>+|}VTF2rVb?5HmNR7t~q{k?~-P7)QK7)lK_*+gpYc72F zxN+snH#ODvsC=#`QHAk%Fx9!g^9*5o{FLo7IPo0TFU&`?=yu0lT->_T80vXJ8s#l5 zXYAQ@#*>{RA-7M8uVX~!;|WPL?Ag%+9LgGYoQS2?u(aepOJ*2*|#PXM|ilBL6@0v5YVG~Uj-q0 z(0S|o%=%q1AM=m(h=01_U{Zs9M4D&1)+xNbQ%~SVzWfny*_d<=tKVb-lGZfsWlqO& z+ZN1IpA_kozuJIn^rwLrIZ=ocy`ztHy9mK%(n0AxJQJjNrD$uUezsg{+yGe8v$+|Z zN4>{0J+ee6ATlEFvDwZw@2J2&GmxSO^r;ra%L&sA`rkPV9dvq2Rg;l;-s4s%;}#Ju zIn&l^fGuXcH;+dMjVIpm5LaLG`WE%&s8g;Y9_c_*a*7y5%g~9c*Uov>YE8Y@{)k2e zyu-N)A#Z%TUH4APhs&{b`pk52O3;OC^4(t<%S4G)`1U0WcDKhZS7QoFv0l~>RFNYu zZ(-tJthjw%nmJWppw;-$BhFfA)^8(~H_g{gDU%nnB1%`Jab*3a6MT9>hS)a9|K3LT z+hlvyRvFBJq;iiJ^2H;#?uR{}9C3Dl%DcN0R!utB{q$Kas}h_d3Yz%f$^tz;^!kUJ zuQxF#XA$6wVO@|wKV8=XrN?*V;RUIr3!ZJS!1LR^H-)|PZhn7A+ux#vyXy8`$CK5H zt9$gt&IS?sqWkXsM>Pt3rxN)hg;Mu6FFZE^%V!}WvbyjV@0UuHbE z>Sdh8A$Lld!_uVMrrmC~Y0pNzySrK_MC?=S{4FY7)3tRp_A~L}labAyv$$dVPmQm| zjK2)o?QP|b-dj=`{V!SuxasNs`BMOdnVDHp9;B)IpB>sGYMgr7@Z);i|2RB``oQoR z9S2H-EL^0`22ivOjw(5#Heyd2=U0BCgmm0AzH1^^}?AiEwo2ml7i)HxVn698Tb z^6KjjoIxdbn)VGrnP=br?7fpIZA#|G^bBUsy#RhHHC9Ex-CL?mU_Pr}{Y*0s4o%cnZ+} zF-2tmMT#Y^bY3;M7ZMU|e;1&Cst@F+fJ*AK%q`B{aa=|L{2Y9t;o$wJ0GPPra(BoH zfc|+P#jL_Skn{ukM?m)uXKAO{pm6;F{d-XZ^iKymcnUa*^G32farj627YcBI|3UwL z_1;LXCoO+R|K$htzsw2HKa?$V0-%4NKj?pa{4gXbR4n$-PXTIV`bU%LU!Ui1o&x@$ z|D-?YUyDrtvy?r5qJQP>&UN&k=s#nh{-4rdLsXGh*qfvk&t$Eq-Jyk_#Nm559E(_W zaToREJbHK@YPdl^VS>S&=;sJ5M1x~~+1Uqy{5Ya`Y95ROt->XB?=r6}VV%|x-D5ad zt+q?R*bT*prZ+x}-Ilf*Hoc?d_iT(y32^j}%L**@PB;zoY3DOlHORyu&WZ%$mFb>C zIc?5L-(~v76RJHEeu`3W4|_2(V2#C&X4q74i7U68jLzvfMOx5`(2fq`JE`^*Z# zyMph#!q|^youz%&#>9VLRO8h;O?WLe2CQ_8R(6c#OvGz?L3mC(a&bc;*o2z9ZZlTQ zzLW9s>qM^T`WWsJC)zy|if%euonKD@Ih8!>V@PHn6D(iB0H-_&ALCf>;zIQy9%VA0 z?8NjM*1Z{z);q*; zZ<^Lb43@ zmjU`0xZ6VQ0v3gSDib;S5Pn&6Ei>~+mat0!o%Ug~07(q7ROeZfoC_!mv zyHYu1)+l&$vSKfoR<@T(5R)`jJ)wAAr6tDv?mKfU_a%rGWEHki#E$acxxwd^?Y4)u zY_QsiR@JAr9W{t4HmG1$2sv%p+mQ(yxm<%JUUOY4dQv}YDWwCHIYrE@e$7g*Ef-BJzNNP=YMhU-y*?CuBhh=0+UHWWMG>m0 z>6mto^H=3(`5x0^${p**EOxo7+u0Sqq&4FWf4g~sHI(nV6#VEgNo!ZDMIfMCgSRAZ z=uS46nCRymFiX-J!$V9hy7|DAY#K986q}$hQTR3E21iy4_T&%Wb1(h624}L4-!Xw$I-q43f)&xZTb|QV* zf1qJ!RI=z?hh2-(I6rPvqu8TLs!q-TKAsYvvS}kD6}{37E8;jJ$w^h1B*3t=*y0q# zd0~Ft6u%OG=_%_ZgUWEc2w{=xYT0DC|HZ=^Q}QY_iN0dKd(AS>lrm3TswW)qE7soP5 zV3FIGKVSc7a#$Ys4UbUhUV%8#np@F3NB3Ud_jD@krtyJqKDNs(kHU*8s-P7!aVv~| zH)ShHE5rA(D|p7hF7o|1U*o28qL2se+nQIO=Po4F();5p8bTHFJhyKi8({d?60hN= zF#D5+b{~Ki3fUeGcy$41P0-Eolg{17ABp_ki2StlTX0+saZ<9+?=WZJTP=AhJV#oa zH~}{vqny+yT10A-$@rK&FghxU3OoE2lBC^0WC+=4)|4X!v_sazGzON#hs=MET$FaG7rJoj^Kpbrhof}#g!zsy zJr`D&N_PDJe+sz&>GqnZ-{zu!|4roYmI7Wyg}+PpijYmf2@;%*75EBxe=yQJm|ww5 z01*7_k+t3Nmsw}O6J!E5IPBiEZ;y)OUNx55*K#@d$I*6m`Z+po&bn^S1|F`)KAz+N zKg(d|1>0Sb;h;F5jShK%jewOW2ue>2&iG!d8JBw@so)|A2ZKxi*aARX50L#}Kt34M z_JCe|_JFqm_T<;x%m+b%TQ!BZS<&IUcbI2w>3@WVz`fd5_C^3MUV)$hcqgDgc>>-E z*n0s;34nS|Ko9*a;AHmY4+D(o)r(OEV2ECgg9BCL?CVq@F)%qZ!#*Gj*nc+i59)Tn zQot^!|K3c`>e>O|4?Cax9lHO^ZtyS8*Z<$VdVr-s^1s1SpuZz(DIy3g1=tPCs#36EFIyeX5f zWXx#HrC`ZiL=(h(F~e`cQa~`UF=z5YHduX(r!z8ae?M3XV63H}4J-xCv0y2%56lHi zK?!Rqu*N7JSZNI1R^8?#i7aLELAl@gAoS{{YSYwxDsIfBAep%oD1KiGwwQQ1^Y=Ud zTnZGJPXdC0QsTdsf@cXqCyy*4BCjrrbpHBk*M)ELzHRh9mqr6!X zcoMKQE#TWetbV1KS@ z2HzUNmendkNNl+IG4LdSvP->-mLfDFwfTel9d8S3>dTX{$KsdZl2mp17DUR1Co9fA zI&%@nOcppyCUUPvU}P@o&vDxr<&Q*mB%SPz8fL79)XxO)7i8mhzFe&t*kVW5f2lKH zH6Uff^Ir3rZfM}wA$7tr%!em(n@rzoFVH_Pk#_$+(D9-p6}H2ck`~%cqj$%A-eygI z_I#>CB_5NxK-B(P^LEVan;Ii_Q7Im_(mki(RdR7kIXLOtnzU$*QRs?W^*Q~J@=zy6 z`653`$`(#VxLrQDgoEjRu{q_^6>mEj05y;hOw*=F9GJ{c+@jZW zV(;Wdn}+!~(q7w>n^i~$APa3)QL1}P&NZ$zFKs#Y!t_L*rF(gTi$y#{q?KvG* z2i<(Ry_qcAjG$L5NT}C|83kpYh{d|$n$hpt}`>z+c!TPkUNV#Vd^%whp{VN?6=+ubmY=CLIO|IKDz=0$Hoa++|720hrIR@^Sjg{oZdNHh`OI! zODW&QP2Vi;npEd4azTfK^8j5hvGptcx|Jq$SLh{p2I^A@uGZUS6}P*IQ-t5>+(Xx5 zi5JaiPUIQn6h~xG!eePoSezvSYs}YMFj81j)Vx~CFDS>;cYjwpp^E4m_{F}li+*cU zVxvp^CHTPHC!S?)9zDU{9v}AYHchZ|Qg&yV6w~*7StV}!%5!lplwk+|R z6m}q=s#-!vhM=p$-&w!rSr(J~-7b*g)ipv*20uMHmfOXtXXF&M$#CR478A!qTq@v? zBXE&$xG@pv0>90vc}Y>@#&2hM%()8Mwr>KE(ou358q{uy7a z?5jhlz!A`b!^xw6A@V$Il%5C0#z&9hyMD`^C%~L1)y?~HA_p`msxcR}340%$RZh;@ zaOAC^mMro-tP+0d?Xw+soIBUxKb{kL_;wc`!+Ckl^{Z08Xp4?7j|vpXDSr2+7AP;C z`}2Gvn0@ov*+}nu_t+^MGCL(CHr9XBs;zS8@Z=nB2wCLMb?fmpvCHOLCNq#ZY-U%YQo zBaPkj5ZgcT>~O z3*dXBHyn};m733=p$6piZr{R#JxYLLx+o(eehJr)Iex0$=k_vex$VVl&ZoOu+~%?> z?z~!WJ82;oHIFF`zEX8+CABwa;p9_t*0%DuZU5O%0$piF)6lE$-Q}-pI=XBm%$?ybt~3fHwdD z2W<`X2ay3Vc2VJuvEU^j4&1Ljd@2tFh-N`@_V4m>J$5xX(W<1MF}7->jE&v6I!rQF{%p}1A&?d}Y09KvA z0uZnticlZJnY5(TbvJdWv|cmCqD8K~xr%+RCx3xIF&&B^z&9<49`6?T(5<>n)z7&5 zR_&7Mi@sVv(!NgI(sSeD*gY6>__rIvSCz+F>Jd?$0&U!VI296?%dA$H5ssSJAL2tK zlj0I=uC4Mr%D-bi2qeb~%WXDkeR}voZv4xTXGreHs5P=lukatc%|;Q40;Jc+Hy_1F zp466Z873`^LML)|#A0L$w7~*kkpD!Nxd7;~7l6Z=Cop*7S0kE|gzfa-hBTQAfHlvn zkI%IH0`Cr~5_W(EKnBVEPH&;=^OEuI-?KYjv|jsYwS#g@vzyio7J%&~wVBVS8quV= zv;|e|ui=wm0Z?fJ3qa=21we(p0BC<-00PQGSMGoXU;;YrK;?IiI0zO1V$M8AU@zZ8 z4pJ~&yO4v=n!+jikU>B1ONZ+w6A?l4ToR}mX3x)eCa6j$yp|;Ol0nszPv79K(kPaI zOW2(4>_^pY!gf&-m7^9;IGQwKQTQxFYYw--<&Y1<&guo=X*4c5u`l7mo2zl@*>0|U6?SI{CX1O) zUqx)jY3;T*77m7~LKSx(=ibQ6eX=O^ZTZ|H+5Tt-pIM#T#_caS?}G3zt;yXcq^`sA z)1q=2mx8>bxbPlPq0F1rC)Q<&lWkCmr=K>-E6oNa9kji9BGbe|JQ*-S{z03Me)SN8 zmkN{4>^yk_B$P9}wyFE>TpzI-qaXvlY#IgY$#Y9h5zjqG)_y3Ux^=&c(8by;wbOJy zB2SzcHtJ6S0j}4m-~gyZFN3OM-LuZ7fr!vkiN(y|cnK0n{Of#Rdx8ZZ6e~tR!flQ6`Y!|o#hCrN(*}p z1bUszeA(W0DqJ1thh^^f$~66MP;1IGVWwHB(%usNR|OBR+gN#Zw?0L^^<}|zH^%}P z_f`9w>lM(+&1DkRWkQCPr>m@I33~*DcItd#nr-@LjoPE8d)mr-_y+ z^Q`(gkL3>*s(oB%b;Qq_zI12s^%BzLT&#cm`Q44Z^xIllQ1|#tNiR{i%adZK4?V{X zC8)TdJGj?i8)!*M`$unFKgr=zcw$ekR)RouNh7xJ0^E!u&#!@@#Ar9f T9jF6jM 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 B4bA`n diff --git a/xmidas/uis/animationWindow.ui b/xmidas/uis/animationWindow.ui index b90a5e0..21e0c3a 100644 --- a/xmidas/uis/animationWindow.ui +++ b/xmidas/uis/animationWindow.ui @@ -1,68 +1,68 @@ - - - Wait - - - - 0 - 0 - 200 - 200 - - - - Wait... - - - Qt::LeftToRight - - - -background-color: rgba(0, 0, 90, 125); - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::NoContextMenu - - - - - - - - - false - - - Qt::AlignCenter - - - - - - - - + + + Wait + + + + 0 + 0 + 200 + 200 + + + + Wait... + + + Qt::LeftToRight + + + +background-color: rgba(0, 0, 90, 125); + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + + + + + + + false + + + Qt::AlignCenter + + + + + + + + diff --git a/xmidas/uis/mainwindow_admin.ui b/xmidas/uis/mainwindow_admin.ui deleted file mode 100644 index a7d17b9..0000000 --- a/xmidas/uis/mainwindow_admin.ui +++ /dev/null @@ -1,2814 +0,0 @@ - - - Ajith - MainWindow - - - true - - - - 0 - 0 - 1364 - 1060 - - - - - 0 - 0 - - - - NSLS-II MIDAS - - - - ../pancake.ico../pancake.ico - - - font: 12pt "Segoe UI"; - - - - - true - - - - 0 - 0 - - - - font: 10pt "Segoe UI"; - - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -} - -QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - - - - 0 - 0 - - - - - - - 0 - - - - - 0 - 0 - 411 - 355 - - - - Adjust Image Dimensions - - - - - - - 0 - 0 - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - 0 - - - 500 - - - 0 - - - 10 - - - - - - - - 0 - 0 - - - - 1 - - - 5000 - - - 1200 - - - 10 - - - - - - - - 0 - 0 - - - - false - - - px - - - 1 - - - 10000 - - - 100 - - - - - - - - 0 - 0 - - - - false - - - px - - - 1 - - - 10000 - - - 100 - - - - - - - - 0 - 0 - - - - false - - - px - - - 0 - - - 10000 - - - 0 - - - - - - - - 0 - 0 - - - - Y Dimension - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - false - - - px - - - 0 - - - 10000 - - - 0 - - - - - - - - 0 - 0 - - - - X Dimension - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Stack Range - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - Adjust the dimensions of the image - - - - - - Update - - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - 0 - 0 - - - - Removes one column/row from all four edges - - - Remove Edges - - - - - - - - - true - - - - 0 - 0 - - - - rebin - - - - - - - true - - - - 0 - 0 - - - - Upscale - - - - - - - - - Ratio - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - 0 - 0 - - - - font: 10pt "MS Shell Dlg 2"; - - - 2 - - - 16 - - - 2 - - - 2 - - - - - - - - - - - - - - - - - - 0 - 0 - 334 - 423 - - - - Image Processing - - - - - - - - - - - - - - 0 - 0 - - - - Covert image dat to log values. - - - - - - Log - - - false - - - - - - - - 0 - 0 - - - - Intensity Normalization with the last Frame. - - - Normalize - - - - - - - - - - 0 - 0 - - - - Reverse the arrays. 012 will be 210 - - - Qt::LeftToRight - - - Transpose - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - Remove Outliers (NSigma) - - - - - - - false - - - - - - 200 - - - 3 - - - Qt::Horizontal - - - - - - - 1 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - Thresholding - - - - - - - - - - - false - - - 100 - - - 5 - - - 5 - - - 5 - - - Qt::Horizontal - - - - - - - 5 - - - - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - uses savgol_filter to smooth data - - - Smoothen - - - - - - - - - false - - - 3 - - - 12 - - - 2 - - - 2 - - - 3 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 2 - - - - - - - Window size - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - - - 0 - 0 - 420 - 344 - - - - Alignment - - - - - - - - - - - Transformations: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - TRANSLATION - - - - - RIGID_BODY - - - - - SCALED_ROTATION - - - - - AFFINE - - - - - BILINEAR - - - - - - - - - - - - Reference: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - previous - - - - - mean - - - - - first - - - - - - - - - - - - - 0 - 0 - - - - Iterative Mode - - - - - - - - - Max Iter. - - - - - - - - 0 - 0 - - - - 2 - - - 24 - - - - - - - - - - - - 0 - 0 - - - - Align - - - - - - - - - - 0 - 0 - - - - Load Reference Stack - - - - - - - No Ref. Available - - - true - - - - - - - - - - 0 - 0 - - - - Save Transformation File - - - - - - - Use - - - - - - - - 0 - 0 - - - - Load Transformation File - - - - - - - - - - - - - - - 0 - 0 - - - - - - - Reset Image - - - - - - - true - - - 2 - - - - - 0 - 0 - 411 - 318 - - - - Component Analysis - - - - - - - - - 0 - 0 - - - - - - - Calculate Components - - - - - - - - 0 - 0 - - - - - - - PCA Scree Plot - - - - - - - - - Method - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - PCA - - - - - NMF - - - - - FastICA - - - - - IncrementalPCA - - - - - TruncatedSVD - - - - - FactorAnalysis - - - - - DictionaryLearning - - - - - - - - - - - - Number of Components - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 1 - - - 4 - - - - - - - - - - - - - 0 - 0 - 411 - 318 - - - - Cluster Analysis - - - - - - - - - 0 - 0 - - - - - - - Calculate Clusters - - - - - - - - 0 - 0 - - - - - - - KMeans Variance Plot - - - - - - - Method - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - KMeans - - - - - MiniBatchKMeans - - - - - MeanShift - - - - - Spectral Clustering - - - - - Correlation-Kmeans - - - - - Affinity Propagation - - - - - - - - Number of Clusters - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 1 - - - 4 - - - - - - - - - - - 0 - 0 - 411 - 318 - - - - XANES Fitting - - - - - - - 11 - - - 11 - - - - - - - - 0 - 0 - - - - - - - Load Energy List - - - - - - - keV - - - - - - - - - - - - 0 - 0 - - - - - - - Load Ref. Spec. - - - - - - - - - - Plot - - - - - - - - - true - - - - 0 - 0 - - - - - - - Fit - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - 0 - 0 - - - - font:12pt "Segoe UI"; - - - - - - - QLayout::SetMaximumSize - - - - - - 0 - 0 - - - - - - - Save Image (.tiff) - - - - - - - 0 - - - - Live - - - - - - - 0 - 0 - - - - Send to Collector - - - - - - - - 0 - 0 - - - - - - - Save - - - - - - - - 0 - 0 - - - - - - - - - Normalized - - - - - - Save - - - - - - - Clear - - - - - - - Norm. to Collector - - - - - - - - 0 - 0 - - - - - - - - - Collector - - - - - - - 0 - 0 - - - - - - - - Clear - - - - - - - Save - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Stack Info - - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - QLayout::SetDefaultConstraint - - - 11 - - - 11 - - - - - - 0 - 0 - - - - ROI Shape - - - - - - - 0 - 0 - - - - Rectangle - - - true - - - - - - - - 0 - 0 - - - - Ellipse - - - - - - - - 0 - 0 - - - - Polygon - - - false - - - - - - - - 0 - 0 - - - - Circle - - - - - - - - 0 - 0 - - - - Line - - - - - - - - 0 - 0 - - - - Show ROI Stack - - - - - - - - - - font: 9pt "Segoe UI"; - - - ROI Positions - - - - - - Spectrum ROI - - - - - - color: rgb(255, 0,0); - - - roi_size - - - - - - - Range (eV): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Size (eV): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - color: rgb(255, 0,0); - - - roix, roiy - - - - - - - - - - Image ROI - - - - - - Position: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - color: rgb(255, 0,0); - - - roix, roiy - - - - - - - - - - Size(pixels): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - color: rgb(255, 0, 0); - - - roi_size - - - - - - - - - - - - - - 0 - 0 - - - - 2 - - - - - 0 - 0 - 361 - 193 - - - - Image Calculations - - - - - - - - - - - - - - - Add ROI_2 - - - - - - - Calculation: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - Subtract - - - - - Divide - - - - - Add - - - - - Compare - - - - - - - - - - - - - 0 - 0 - 361 - 193 - - - - Spectrum Calculations - - - - - - - - - - - - - 0 - 0 - - - - Add ROI 2 - - - true - - - false - - - - - - - - - - 0 - 0 - - - - Calculation - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - Divide - - - - - Subtract - - - - - Add - - - - - Correlation Plot - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - 0 - 433 - 308 - - - - XANES Normalization - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - 1 - - - 5 - - - - - - - - 0 - 0 - - - - Eo - - - - - - - - 0 - 0 - - - - eV - - - 1000.000000000000000 - - - 20000.000000000000000 - - - 7125.000000000000000 - - - - - - - - 0 - 0 - - - - calculate the energy point with maximum derivative - - - Auto Eo - - - - - - - - 0 - 0 - - - - eV - - - 2 - - - -500.000000000000000 - - - 500.000000000000000 - - - 1.000000000000000 - - - -50.000000000000000 - - - - - - - - 0 - 0 - - - - Pre-edge - - - - - - - - 0 - 0 - - - - eV - - - -500.000000000000000 - - - 500.000000000000000 - - - -10.000000000000000 - - - - - - - - 0 - 0 - - - - Post-edge - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Norm. Order - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - eV - - - 0.000000000000000 - - - 1000.000000000000000 - - - 25.000000000000000 - - - - - - - - 0 - 0 - - - - <html><head/><body><p> For mbak algorithm only, must be in <span style=" font-weight:600; font-style:italic;">element&lt;space&gt;edge</span> format</p></body></html> - - - Fe K - - - - - - - - 0 - 0 - - - - Element: - - - - - - - - 0 - 0 - - - - eV - - - 0.000000000000000 - - - 1500.000000000000000 - - - 75.000000000000000 - - - - - - - Initial Guess - - - - - - - - 0 - 0 - - - - Apply to Spectrum - - - - - - - Apply to Stack - - - - - - - - - - - - - - Available Stacks - - - - - - - - - - - - 0 - 0 - 1364 - 34 - - - - - Help - - - - - - - File - - - - - - - - - - - true - - - Tools - - - - - - - View - - - - Plot Background - - - - - - - - - - Window Background - - - - - - - - Change_Plot_Line_Width - - - - - - - - - - - - - - - - - Spectrum - - - - - - - - - Image - - - - - - - - - - - - - - - - Open PDF - - - - - Open in GitHub (most updated) - - - - - true - - - Open Image Data - - - Support all tiff and specific h5 files - - - Ctrl+O - - - - - Close - - - - - Exit - - - Ctrl+Q - - - - - false - - - Export Tiff Stack - - - Save the displayed/Modified stack as a tiff file - - - Ctrl+S - - - - - Open PyXRF - - - - - Open Image J - - - - - Open TomViz - - - - - Open Mantis - - - - - Open Athena - - - - - DataBroker - - - - - Open HXN DB - - - - - false - - - Load Energy - - - Load list of energies for XANES stack. Supports only .txt fromat - - - Ctrl+E - - - - - Create a Virtual Stack - - - Create a stack from multiple tiff images of same shape - - - Ctrl+M - - - - - true - - - Open Mask Generator - - - A new window will be opened to creat threshold based masks - - - - - Create elist from log file - - - - - false - - - Save Energy List - - - - - true - - - MultiColorView - - - A new window will be opened to align images in a stack - - - - - White - - - - - true - - - true - - - Black - - - - - Red - - - - - Yellow - - - - - Blue - - - - - false - - - false - - - Dark Mode - - - - - Black - - - - - false - - - false - - - Default - - - - - Vivid - - - - - Save Sum Image - - - - - Subtract ROI as Background - - - - - Export Norm. Params - - - - - 2 - - - - - 4 - - - - - 6 - - - - - 10 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 8 - - - - - 10 - - - - - 2 - - - - - Import Norm. Params - - - - - 1 - - - - - Save Sum Spectrum - - - - - Save Mean Spectrum - - - - - Save Current Image as Mask - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
diff --git a/xmidas/uis/maskedScatterPlotFit.ui b/xmidas/uis/maskedScatterPlotFit.ui index e8bd894..8ad2145 100644 --- a/xmidas/uis/maskedScatterPlotFit.ui +++ b/xmidas/uis/maskedScatterPlotFit.ui @@ -1,187 +1,187 @@ - - - CorrelationPlot - - - - 0 - 0 - 967 - 816 - - - - Correlation Plot - - - font: 12pt "MS Shell Dlg 2"; - - - - - 10 - - - 25 - - - 10 - - - 10 - - - - - Scatter Plot - - - - - - - 0 - 0 - - - - - - - - - - - Mask - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Fit Results - - - - - - Copy - - - - - - - - 0 - 0 - - - - font: 11pt "Consolas"; - - - true - - - - - - - Export - - - - - - - - - - Masked Image1 - - - - - - - 0 - 0 - - - - - - - - - - - - - - 0 - 0 - 967 - 25 - - - - - Export - - - - - - - - - - Scatter Plot+Fit - - - - - Mask - - - - - Masked Image - - - - - - ImageView - QGraphicsView -
pyqtgraph
-
- - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + CorrelationPlot + + + + 0 + 0 + 967 + 816 + + + + Correlation Plot + + + font: 12pt "MS Shell Dlg 2"; + + + + + 10 + + + 25 + + + 10 + + + 10 + + + + + Scatter Plot + + + + + + + 0 + 0 + + + + + + + + + + + Mask + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Fit Results + + + + + + Copy + + + + + + + + 0 + 0 + + + + font: 11pt "Consolas"; + + + true + + + + + + + Export + + + + + + + + + + Masked Image1 + + + + + + + 0 + 0 + + + + + + + + + + + + + + 0 + 0 + 967 + 25 + + + + + Export + + + + + + + + + + Scatter Plot+Fit + + + + + Mask + + + + + Masked Image + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+ + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/midas.py b/xmidas/uis/midas.py deleted file mode 100644 index 4b0ea40..0000000 --- a/xmidas/uis/midas.py +++ /dev/null @@ -1,1048 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'mainwindow_admin.ui' -# -# Created by: PyQt5 UI code generator 5.14.1 -# -# 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.setEnabled(True) - MainWindow.resize(1246, 936) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) - MainWindow.setSizePolicy(sizePolicy) - MainWindow.setStyleSheet("font: 10pt \"Segoe UI\";\n" -"\n" -"\n" -"QSlider::groove:horizontal {\n" -"border: 1px solid #bbb;\n" -"background: white;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::sub-page:horizontal {\n" -"background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,\n" -" stop: 0 #66e, stop: 1 #bbf);\n" -"background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1,\n" -" stop: 0 #bbf, stop: 1 #55f);\n" -"border: 1px solid #777;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::add-page:horizontal {\n" -"background: #fff;\n" -"border: 1px solid #777;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::handle:horizontal {\n" -"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,\n" -" stop:0 #eee, stop:1 #ccc);\n" -"border: 1px solid #777;\n" -"width: 13px;\n" -"margin-top: -2px;\n" -"margin-bottom: -2px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::handle:horizontal:hover {\n" -"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,\n" -" stop:0 #fff, stop:1 #ddd);\n" -"border: 1px solid #444;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::sub-page:horizontal:disabled {\n" -"background: #bbb;\n" -"border-color: #999;\n" -"}\n" -"\n" -"QSlider::add-page:horizontal:disabled {\n" -"background: #eee;\n" -"border-color: #999;\n" -"}\n" -"\n" -"QSlider::handle:horizontal:disabled {\n" -"background: #eee;\n" -"border: 1px solid #aaa;\n" -"border-radius: 4px;\n" -"}") - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) - self.centralwidget.setSizePolicy(sizePolicy) - self.centralwidget.setStyleSheet("QSlider::groove:horizontal {\n" -"border: 1px solid #bbb;\n" -"background: white;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::sub-page:horizontal {\n" -"background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,\n" -" stop: 0 #66e, stop: 1 #bbf);\n" -"background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1,\n" -" stop: 0 #bbf, stop: 1 #55f);\n" -"border: 1px solid #777;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::add-page:horizontal {\n" -"background: #fff;\n" -"border: 1px solid #777;\n" -"height: 10px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::handle:horizontal {\n" -"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,\n" -" stop:0 #eee, stop:1 #ccc);\n" -"border: 1px solid #777;\n" -"width: 13px;\n" -"margin-top: -2px;\n" -"margin-bottom: -2px;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::handle:horizontal:hover {\n" -"background: qlineargradient(x1:0, y1:0, x2:1, y2:1,\n" -" stop:0 #fff, stop:1 #ddd);\n" -"border: 1px solid #444;\n" -"border-radius: 4px;\n" -"}\n" -"\n" -"QSlider::sub-page:horizontal:disabled {\n" -"background: #bbb;\n" -"border-color: #999;\n" -"}\n" -"\n" -"QSlider::add-page:horizontal:disabled {\n" -"background: #eee;\n" -"border-color: #999;\n" -"}\n" -"\n" -"QSlider::handle:horizontal:disabled {\n" -"background: #eee;\n" -"border: 1px solid #aaa;\n" -"border-radius: 4px;\n" -"}") - self.centralwidget.setObjectName("centralwidget") - self.gridLayout_16 = QtWidgets.QGridLayout(self.centralwidget) - self.gridLayout_16.setObjectName("gridLayout_16") - self.scrollArea = QtWidgets.QScrollArea(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) - self.scrollArea.setSizePolicy(sizePolicy) - self.scrollArea.setStyleSheet("font:12pt \"Segoe UI\";") - self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) - self.scrollArea.setWidgetResizable(True) - self.scrollArea.setAlignment(QtCore.Qt.AlignCenter) - self.scrollArea.setObjectName("scrollArea") - self.scrollAreaWidgetContents = QtWidgets.QWidget() - self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 321, 855)) - self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") - self.gridLayout_9 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents) - self.gridLayout_9.setObjectName("gridLayout_9") - self.gridLayout_13 = QtWidgets.QGridLayout() - self.gridLayout_13.setObjectName("gridLayout_13") - self.pb_reset_img = QtWidgets.QPushButton(self.scrollAreaWidgetContents) - self.pb_reset_img.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_reset_img.setObjectName("pb_reset_img") - self.gridLayout_13.addWidget(self.pb_reset_img, 0, 0, 1, 1) - self.gridLayout_9.addLayout(self.gridLayout_13, 1, 0, 1, 1) - self.toolBox = QtWidgets.QToolBox(self.scrollAreaWidgetContents) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.toolBox.sizePolicy().hasHeightForWidth()) - self.toolBox.setSizePolicy(sizePolicy) - self.toolBox.setStyleSheet("font: 10pt \"Segoe UI\";") - self.toolBox.setObjectName("toolBox") - self.page = QtWidgets.QWidget() - self.page.setGeometry(QtCore.QRect(0, 0, 368, 331)) - self.page.setObjectName("page") - self.gridLayout_31 = QtWidgets.QGridLayout(self.page) - self.gridLayout_31.setObjectName("gridLayout_31") - self.frame_7 = QtWidgets.QFrame(self.page) - self.frame_7.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_7.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_7.setObjectName("frame_7") - self.gridLayout_30 = QtWidgets.QGridLayout(self.frame_7) - self.gridLayout_30.setObjectName("gridLayout_30") - self.gridLayout_17 = QtWidgets.QGridLayout() - self.gridLayout_17.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) - self.gridLayout_17.setObjectName("gridLayout_17") - self.sb_zrange1 = QtWidgets.QSpinBox(self.frame_7) - self.sb_zrange1.setMinimum(0) - self.sb_zrange1.setMaximum(500) - self.sb_zrange1.setProperty("value", 0) - self.sb_zrange1.setDisplayIntegerBase(10) - self.sb_zrange1.setObjectName("sb_zrange1") - self.gridLayout_17.addWidget(self.sb_zrange1, 0, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.frame_7) - self.label_5.setAlignment(QtCore.Qt.AlignCenter) - self.label_5.setObjectName("label_5") - self.gridLayout_17.addWidget(self.label_5, 1, 2, 1, 1) - self.label_9 = QtWidgets.QLabel(self.frame_7) - self.label_9.setAlignment(QtCore.Qt.AlignCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_17.addWidget(self.label_9, 2, 2, 1, 1) - self.sb_yrange2 = QtWidgets.QSpinBox(self.frame_7) - self.sb_yrange2.setReadOnly(False) - self.sb_yrange2.setMinimum(1) - self.sb_yrange2.setMaximum(10000) - self.sb_yrange2.setProperty("value", 100) - self.sb_yrange2.setObjectName("sb_yrange2") - self.gridLayout_17.addWidget(self.sb_yrange2, 2, 3, 1, 1) - self.sb_zrange2 = QtWidgets.QSpinBox(self.frame_7) - self.sb_zrange2.setMinimum(1) - self.sb_zrange2.setMaximum(5000) - self.sb_zrange2.setProperty("value", 1200) - self.sb_zrange2.setDisplayIntegerBase(10) - self.sb_zrange2.setObjectName("sb_zrange2") - self.gridLayout_17.addWidget(self.sb_zrange2, 0, 3, 1, 1) - self.sb_xrange1 = QtWidgets.QSpinBox(self.frame_7) - self.sb_xrange1.setReadOnly(False) - self.sb_xrange1.setMinimum(0) - self.sb_xrange1.setMaximum(10000) - self.sb_xrange1.setProperty("value", 0) - self.sb_xrange1.setObjectName("sb_xrange1") - self.gridLayout_17.addWidget(self.sb_xrange1, 1, 1, 1, 1) - self.sb_xrange2 = QtWidgets.QSpinBox(self.frame_7) - self.sb_xrange2.setReadOnly(False) - self.sb_xrange2.setMinimum(1) - self.sb_xrange2.setMaximum(10000) - self.sb_xrange2.setProperty("value", 100) - self.sb_xrange2.setObjectName("sb_xrange2") - self.gridLayout_17.addWidget(self.sb_xrange2, 1, 3, 1, 1) - self.label_22 = QtWidgets.QLabel(self.frame_7) - self.label_22.setAlignment(QtCore.Qt.AlignCenter) - self.label_22.setObjectName("label_22") - self.gridLayout_17.addWidget(self.label_22, 0, 2, 1, 1) - self.label_19 = QtWidgets.QLabel(self.frame_7) - self.label_19.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_19.setObjectName("label_19") - self.gridLayout_17.addWidget(self.label_19, 1, 0, 1, 1) - self.sb_yrange1 = QtWidgets.QSpinBox(self.frame_7) - self.sb_yrange1.setReadOnly(False) - self.sb_yrange1.setMinimum(0) - self.sb_yrange1.setMaximum(10000) - self.sb_yrange1.setProperty("value", 0) - self.sb_yrange1.setObjectName("sb_yrange1") - self.gridLayout_17.addWidget(self.sb_yrange1, 2, 1, 1, 1) - self.label_15 = QtWidgets.QLabel(self.frame_7) - self.label_15.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_15.setObjectName("label_15") - self.gridLayout_17.addWidget(self.label_15, 2, 0, 1, 1) - self.label_20 = QtWidgets.QLabel(self.frame_7) - self.label_20.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_20.setObjectName("label_20") - self.gridLayout_17.addWidget(self.label_20, 0, 0, 1, 1) - self.gridLayout_30.addLayout(self.gridLayout_17, 0, 0, 1, 1) - self.pb_crop = QtWidgets.QPushButton(self.frame_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_crop.sizePolicy().hasHeightForWidth()) - self.pb_crop.setSizePolicy(sizePolicy) - self.pb_crop.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_crop.setObjectName("pb_crop") - self.gridLayout_30.addWidget(self.pb_crop, 1, 0, 1, 1) - self.gridLayout_31.addWidget(self.frame_7, 1, 0, 1, 1) - self.frame_6 = QtWidgets.QFrame(self.page) - self.frame_6.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_6.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_6.setObjectName("frame_6") - self.gridLayout_7 = QtWidgets.QGridLayout(self.frame_6) - self.gridLayout_7.setObjectName("gridLayout_7") - self.horizontalLayout_9 = QtWidgets.QHBoxLayout() - self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.cb_rebin = QtWidgets.QCheckBox(self.frame_6) - self.cb_rebin.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_rebin.sizePolicy().hasHeightForWidth()) - self.cb_rebin.setSizePolicy(sizePolicy) - self.cb_rebin.setObjectName("cb_rebin") - self.horizontalLayout_9.addWidget(self.cb_rebin) - self.horizontalLayout_10 = QtWidgets.QHBoxLayout() - self.horizontalLayout_10.setObjectName("horizontalLayout_10") - self.cb_upscale = QtWidgets.QCheckBox(self.frame_6) - self.cb_upscale.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_upscale.sizePolicy().hasHeightForWidth()) - self.cb_upscale.setSizePolicy(sizePolicy) - self.cb_upscale.setObjectName("cb_upscale") - self.horizontalLayout_10.addWidget(self.cb_upscale) - self.horizontalLayout_9.addLayout(self.horizontalLayout_10) - self.sb_scaling_factor = QtWidgets.QSpinBox(self.frame_6) - self.sb_scaling_factor.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sb_scaling_factor.sizePolicy().hasHeightForWidth()) - self.sb_scaling_factor.setSizePolicy(sizePolicy) - self.sb_scaling_factor.setStyleSheet("font: 10pt \"MS Shell Dlg 2\";") - self.sb_scaling_factor.setMinimum(2) - self.sb_scaling_factor.setMaximum(16) - self.sb_scaling_factor.setSingleStep(2) - self.sb_scaling_factor.setProperty("value", 2) - self.sb_scaling_factor.setObjectName("sb_scaling_factor") - self.horizontalLayout_9.addWidget(self.sb_scaling_factor) - self.gridLayout_7.addLayout(self.horizontalLayout_9, 2, 0, 1, 1) - self.cb_remove_edges = QtWidgets.QCheckBox(self.frame_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_remove_edges.sizePolicy().hasHeightForWidth()) - self.cb_remove_edges.setSizePolicy(sizePolicy) - self.cb_remove_edges.setObjectName("cb_remove_edges") - self.gridLayout_7.addWidget(self.cb_remove_edges, 0, 0, 1, 1) - self.gridLayout_31.addWidget(self.frame_6, 2, 0, 1, 1) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_31.addItem(spacerItem, 0, 0, 1, 1) - spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_31.addItem(spacerItem1, 3, 0, 1, 1) - self.toolBox.addItem(self.page, "") - self.page_2 = QtWidgets.QWidget() - self.page_2.setGeometry(QtCore.QRect(0, 0, 299, 462)) - self.page_2.setObjectName("page_2") - self.gridLayout_8 = QtWidgets.QGridLayout(self.page_2) - self.gridLayout_8.setObjectName("gridLayout_8") - self.groupBox = QtWidgets.QGroupBox(self.page_2) - self.groupBox.setTitle("") - self.groupBox.setObjectName("groupBox") - self.gridLayout_28 = QtWidgets.QGridLayout(self.groupBox) - self.gridLayout_28.setObjectName("gridLayout_28") - self.frame = QtWidgets.QFrame(self.groupBox) - self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame.setObjectName("frame") - self.gridLayout_27 = QtWidgets.QGridLayout(self.frame) - self.gridLayout_27.setObjectName("gridLayout_27") - self.groupBox_9 = QtWidgets.QGroupBox(self.frame) - self.groupBox_9.setObjectName("groupBox_9") - self.gridLayout_24 = QtWidgets.QGridLayout(self.groupBox_9) - self.gridLayout_24.setObjectName("gridLayout_24") - self.verticalLayout_3 = QtWidgets.QVBoxLayout() - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.horizontalLayout_7 = QtWidgets.QHBoxLayout() - self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.cb_log = QtWidgets.QCheckBox(self.groupBox_9) - self.cb_log.setStyleSheet("") - self.cb_log.setChecked(False) - self.cb_log.setObjectName("cb_log") - self.horizontalLayout_7.addWidget(self.cb_log) - self.cb_norm = QtWidgets.QCheckBox(self.groupBox_9) - self.cb_norm.setObjectName("cb_norm") - self.horizontalLayout_7.addWidget(self.cb_norm) - self.verticalLayout_3.addLayout(self.horizontalLayout_7) - self.cb_transpose = QtWidgets.QCheckBox(self.groupBox_9) - self.cb_transpose.setLayoutDirection(QtCore.Qt.LeftToRight) - self.cb_transpose.setObjectName("cb_transpose") - self.verticalLayout_3.addWidget(self.cb_transpose) - self.gridLayout_24.addLayout(self.verticalLayout_3, 0, 0, 1, 1) - self.gridLayout_27.addWidget(self.groupBox_9, 0, 0, 1, 1) - self.gridLayout_28.addWidget(self.frame, 0, 0, 1, 1) - self.frame_4 = QtWidgets.QFrame(self.groupBox) - self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_4.setObjectName("frame_4") - self.gridLayout_25 = QtWidgets.QGridLayout(self.frame_4) - self.gridLayout_25.setObjectName("gridLayout_25") - self.gridLayout_21 = QtWidgets.QGridLayout() - self.gridLayout_21.setObjectName("gridLayout_21") - self.cb_remove_outliers = QtWidgets.QCheckBox(self.frame_4) - self.cb_remove_outliers.setObjectName("cb_remove_outliers") - self.gridLayout_21.addWidget(self.cb_remove_outliers, 0, 0, 1, 1) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.hs_nsigma = QtWidgets.QSlider(self.frame_4) - self.hs_nsigma.setEnabled(False) - self.hs_nsigma.setStyleSheet("border-color: rgb(0, 85, 255);") - self.hs_nsigma.setMaximum(200) - self.hs_nsigma.setProperty("value", 3) - self.hs_nsigma.setOrientation(QtCore.Qt.Horizontal) - self.hs_nsigma.setObjectName("hs_nsigma") - self.horizontalLayout.addWidget(self.hs_nsigma) - self.label_nsigma = QtWidgets.QLabel(self.frame_4) - self.label_nsigma.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_nsigma.setObjectName("label_nsigma") - self.horizontalLayout.addWidget(self.label_nsigma) - self.gridLayout_21.addLayout(self.horizontalLayout, 1, 0, 1, 1) - self.gridLayout_25.addLayout(self.gridLayout_21, 0, 0, 1, 1) - self.gridLayout_28.addWidget(self.frame_4, 1, 0, 1, 1) - self.frame_3 = QtWidgets.QFrame(self.groupBox) - self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_3.setObjectName("frame_3") - self.gridLayout_26 = QtWidgets.QGridLayout(self.frame_3) - self.gridLayout_26.setObjectName("gridLayout_26") - self.gridLayout_5 = QtWidgets.QGridLayout() - self.gridLayout_5.setObjectName("gridLayout_5") - self.horizontalLayout_3 = QtWidgets.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.cb_remove_bg = QtWidgets.QCheckBox(self.frame_3) - self.cb_remove_bg.setObjectName("cb_remove_bg") - self.horizontalLayout_3.addWidget(self.cb_remove_bg) - self.gridLayout_5.addLayout(self.horizontalLayout_3, 0, 0, 1, 1) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.hs_bg_threshold = QtWidgets.QSlider(self.frame_3) - self.hs_bg_threshold.setEnabled(False) - self.hs_bg_threshold.setMaximum(100) - self.hs_bg_threshold.setSingleStep(5) - self.hs_bg_threshold.setPageStep(5) - self.hs_bg_threshold.setProperty("value", 5) - self.hs_bg_threshold.setOrientation(QtCore.Qt.Horizontal) - self.hs_bg_threshold.setObjectName("hs_bg_threshold") - self.horizontalLayout_2.addWidget(self.hs_bg_threshold) - self.label_bg_threshold = QtWidgets.QLabel(self.frame_3) - self.label_bg_threshold.setObjectName("label_bg_threshold") - self.horizontalLayout_2.addWidget(self.label_bg_threshold) - self.gridLayout_5.addLayout(self.horizontalLayout_2, 1, 0, 1, 1) - self.gridLayout_26.addLayout(self.gridLayout_5, 0, 0, 1, 1) - self.gridLayout_28.addWidget(self.frame_3, 2, 0, 1, 1) - self.frame_2 = QtWidgets.QFrame(self.groupBox) - self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_2.setObjectName("frame_2") - self.gridLayout_3 = QtWidgets.QGridLayout(self.frame_2) - self.gridLayout_3.setObjectName("gridLayout_3") - self.cb_smooth = QtWidgets.QCheckBox(self.frame_2) - self.cb_smooth.setObjectName("cb_smooth") - self.gridLayout_3.addWidget(self.cb_smooth, 0, 0, 1, 1) - self.horizontalLayout_4 = QtWidgets.QHBoxLayout() - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.hs_smooth_size = QtWidgets.QSlider(self.frame_2) - self.hs_smooth_size.setEnabled(False) - self.hs_smooth_size.setMinimum(3) - self.hs_smooth_size.setMaximum(12) - self.hs_smooth_size.setSingleStep(2) - self.hs_smooth_size.setPageStep(2) - self.hs_smooth_size.setProperty("value", 3) - self.hs_smooth_size.setOrientation(QtCore.Qt.Horizontal) - self.hs_smooth_size.setTickPosition(QtWidgets.QSlider.TicksBelow) - self.hs_smooth_size.setTickInterval(2) - self.hs_smooth_size.setObjectName("hs_smooth_size") - self.horizontalLayout_4.addWidget(self.hs_smooth_size) - self.smooth_winow_size = QtWidgets.QLabel(self.frame_2) - self.smooth_winow_size.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.smooth_winow_size.setObjectName("smooth_winow_size") - self.horizontalLayout_4.addWidget(self.smooth_winow_size) - self.gridLayout_3.addLayout(self.horizontalLayout_4, 1, 0, 1, 1) - self.gridLayout_28.addWidget(self.frame_2, 3, 0, 1, 1) - self.gridLayout_8.addWidget(self.groupBox, 0, 0, 1, 1) - self.toolBox.addItem(self.page_2, "") - self.gridLayout_9.addWidget(self.toolBox, 0, 0, 1, 1) - spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - self.gridLayout_9.addItem(spacerItem2, 2, 0, 1, 1) - self.toolBox_2 = QtWidgets.QToolBox(self.scrollAreaWidgetContents) - self.toolBox_2.setObjectName("toolBox_2") - self.page_5 = QtWidgets.QWidget() - self.page_5.setGeometry(QtCore.QRect(0, 0, 298, 189)) - self.page_5.setObjectName("page_5") - self.gridLayout_15 = QtWidgets.QGridLayout(self.page_5) - self.gridLayout_15.setObjectName("gridLayout_15") - self.gridLayout = QtWidgets.QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.pb_calc_components = QtWidgets.QPushButton(self.page_5) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_calc_components.sizePolicy().hasHeightForWidth()) - self.pb_calc_components.setSizePolicy(sizePolicy) - self.pb_calc_components.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_calc_components.setObjectName("pb_calc_components") - self.gridLayout.addWidget(self.pb_calc_components, 0, 0, 1, 1) - self.pb_pca_scree = QtWidgets.QPushButton(self.page_5) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_pca_scree.sizePolicy().hasHeightForWidth()) - self.pb_pca_scree.setSizePolicy(sizePolicy) - self.pb_pca_scree.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_pca_scree.setObjectName("pb_pca_scree") - self.gridLayout.addWidget(self.pb_pca_scree, 1, 0, 1, 1) - self.horizontalLayout_5 = QtWidgets.QHBoxLayout() - self.horizontalLayout_5.setObjectName("horizontalLayout_5") - self.label_11 = QtWidgets.QLabel(self.page_5) - self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_11.setObjectName("label_11") - self.horizontalLayout_5.addWidget(self.label_11) - self.cb_comp_method = QtWidgets.QComboBox(self.page_5) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_comp_method.sizePolicy().hasHeightForWidth()) - self.cb_comp_method.setSizePolicy(sizePolicy) - self.cb_comp_method.setObjectName("cb_comp_method") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.cb_comp_method.addItem("") - self.horizontalLayout_5.addWidget(self.cb_comp_method) - self.gridLayout.addLayout(self.horizontalLayout_5, 2, 0, 1, 1) - self.horizontalLayout_6 = QtWidgets.QHBoxLayout() - self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.label = QtWidgets.QLabel(self.page_5) - self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label.setObjectName("label") - self.horizontalLayout_6.addWidget(self.label) - self.sb_ncomp = QtWidgets.QSpinBox(self.page_5) - self.sb_ncomp.setMinimum(1) - self.sb_ncomp.setProperty("value", 4) - self.sb_ncomp.setObjectName("sb_ncomp") - self.horizontalLayout_6.addWidget(self.sb_ncomp) - self.gridLayout.addLayout(self.horizontalLayout_6, 3, 0, 1, 1) - self.gridLayout_15.addLayout(self.gridLayout, 0, 0, 1, 1) - self.toolBox_2.addItem(self.page_5, "") - self.page_6 = QtWidgets.QWidget() - self.page_6.setGeometry(QtCore.QRect(0, 0, 308, 185)) - self.page_6.setObjectName("page_6") - self.gridLayout_18 = QtWidgets.QGridLayout(self.page_6) - self.gridLayout_18.setObjectName("gridLayout_18") - self.gridLayout_4 = QtWidgets.QGridLayout() - self.gridLayout_4.setObjectName("gridLayout_4") - self.pb_calc_cluster = QtWidgets.QPushButton(self.page_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_calc_cluster.sizePolicy().hasHeightForWidth()) - self.pb_calc_cluster.setSizePolicy(sizePolicy) - self.pb_calc_cluster.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_calc_cluster.setObjectName("pb_calc_cluster") - self.gridLayout_4.addWidget(self.pb_calc_cluster, 0, 0, 1, 3) - self.pb_kmeans_elbow = QtWidgets.QPushButton(self.page_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_kmeans_elbow.sizePolicy().hasHeightForWidth()) - self.pb_kmeans_elbow.setSizePolicy(sizePolicy) - self.pb_kmeans_elbow.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_kmeans_elbow.setObjectName("pb_kmeans_elbow") - self.gridLayout_4.addWidget(self.pb_kmeans_elbow, 1, 0, 1, 3) - self.label_10 = QtWidgets.QLabel(self.page_6) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_4.addWidget(self.label_10, 2, 0, 1, 1) - self.cb_clust_method = QtWidgets.QComboBox(self.page_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_clust_method.sizePolicy().hasHeightForWidth()) - self.cb_clust_method.setSizePolicy(sizePolicy) - self.cb_clust_method.setObjectName("cb_clust_method") - self.cb_clust_method.addItem("") - self.cb_clust_method.addItem("") - self.cb_clust_method.addItem("") - self.cb_clust_method.addItem("") - self.cb_clust_method.addItem("") - self.cb_clust_method.addItem("") - self.gridLayout_4.addWidget(self.cb_clust_method, 2, 1, 1, 2) - self.label_2 = QtWidgets.QLabel(self.page_6) - self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_2.setObjectName("label_2") - self.gridLayout_4.addWidget(self.label_2, 3, 0, 1, 2) - self.sb_ncluster = QtWidgets.QSpinBox(self.page_6) - self.sb_ncluster.setMinimum(1) - self.sb_ncluster.setProperty("value", 4) - self.sb_ncluster.setObjectName("sb_ncluster") - self.gridLayout_4.addWidget(self.sb_ncluster, 3, 2, 1, 1) - self.gridLayout_18.addLayout(self.gridLayout_4, 0, 0, 1, 1) - self.toolBox_2.addItem(self.page_6, "") - self.page_7 = QtWidgets.QWidget() - self.page_7.setGeometry(QtCore.QRect(0, 0, 299, 156)) - self.page_7.setObjectName("page_7") - self.gridLayout_20 = QtWidgets.QGridLayout(self.page_7) - self.gridLayout_20.setObjectName("gridLayout_20") - self.gridLayout_2 = QtWidgets.QGridLayout() - self.gridLayout_2.setObjectName("gridLayout_2") - self.pb_elist_xanes = QtWidgets.QPushButton(self.page_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_elist_xanes.sizePolicy().hasHeightForWidth()) - self.pb_elist_xanes.setSizePolicy(sizePolicy) - self.pb_elist_xanes.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_elist_xanes.setObjectName("pb_elist_xanes") - self.gridLayout_2.addWidget(self.pb_elist_xanes, 0, 0, 1, 2) - self.cb_kev_flag = QtWidgets.QCheckBox(self.page_7) - self.cb_kev_flag.setObjectName("cb_kev_flag") - self.gridLayout_2.addWidget(self.cb_kev_flag, 0, 2, 1, 1) - self.pb_ref_xanes = QtWidgets.QPushButton(self.page_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_ref_xanes.sizePolicy().hasHeightForWidth()) - self.pb_ref_xanes.setSizePolicy(sizePolicy) - self.pb_ref_xanes.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_ref_xanes.setObjectName("pb_ref_xanes") - self.gridLayout_2.addWidget(self.pb_ref_xanes, 1, 0, 1, 2) - self.pb_plot_refs = QtWidgets.QPushButton(self.page_7) - self.pb_plot_refs.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);") - self.pb_plot_refs.setObjectName("pb_plot_refs") - self.gridLayout_2.addWidget(self.pb_plot_refs, 1, 2, 1, 1) - self.cb_xanes_fitting_method = QtWidgets.QComboBox(self.page_7) - self.cb_xanes_fitting_method.setObjectName("cb_xanes_fitting_method") - self.cb_xanes_fitting_method.addItem("") - self.gridLayout_2.addWidget(self.cb_xanes_fitting_method, 2, 0, 1, 1) - self.pb_xanes_fit = QtWidgets.QPushButton(self.page_7) - self.pb_xanes_fit.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pb_xanes_fit.sizePolicy().hasHeightForWidth()) - self.pb_xanes_fit.setSizePolicy(sizePolicy) - self.pb_xanes_fit.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);\n" -"") - self.pb_xanes_fit.setObjectName("pb_xanes_fit") - self.gridLayout_2.addWidget(self.pb_xanes_fit, 2, 1, 1, 2) - self.gridLayout_20.addLayout(self.gridLayout_2, 0, 0, 1, 1) - spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_20.addItem(spacerItem3, 1, 0, 1, 1) - self.toolBox_2.addItem(self.page_7, "") - self.gridLayout_9.addWidget(self.toolBox_2, 3, 0, 1, 1) - self.scrollArea.setWidget(self.scrollAreaWidgetContents) - self.gridLayout_16.addWidget(self.scrollArea, 0, 0, 1, 1) - self.groupBox_6 = QtWidgets.QGroupBox(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) - self.groupBox_6.setSizePolicy(sizePolicy) - self.groupBox_6.setStyleSheet("font:12pt \"Segoe UI\";") - self.groupBox_6.setTitle("") - self.groupBox_6.setObjectName("groupBox_6") - self.gridLayout_29 = QtWidgets.QGridLayout(self.groupBox_6) - self.gridLayout_29.setObjectName("gridLayout_29") - self.frame_5 = QtWidgets.QFrame(self.groupBox_6) - self.frame_5.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_5.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_5.setObjectName("frame_5") - self.gridLayout_23 = QtWidgets.QGridLayout(self.frame_5) - self.gridLayout_23.setObjectName("gridLayout_23") - self.groupBox_8 = QtWidgets.QGroupBox(self.frame_5) - self.groupBox_8.setStyleSheet("font: 9pt \"Segoe UI\";") - self.groupBox_8.setObjectName("groupBox_8") - self.gridLayout_19 = QtWidgets.QGridLayout(self.groupBox_8) - self.gridLayout_19.setObjectName("gridLayout_19") - self.groupBox_5 = QtWidgets.QGroupBox(self.groupBox_8) - self.groupBox_5.setObjectName("groupBox_5") - self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_5) - self.gridLayout_11.setObjectName("gridLayout_11") - self.le_spec_roi_size = QtWidgets.QLabel(self.groupBox_5) - self.le_spec_roi_size.setStyleSheet("color: rgb(255, 0,0);") - self.le_spec_roi_size.setObjectName("le_spec_roi_size") - self.gridLayout_11.addWidget(self.le_spec_roi_size, 1, 1, 1, 1) - self.label_8 = QtWidgets.QLabel(self.groupBox_5) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_11.addWidget(self.label_8, 0, 0, 1, 1) - self.label_29 = QtWidgets.QLabel(self.groupBox_5) - self.label_29.setStyleSheet("") - self.label_29.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_29.setObjectName("label_29") - self.gridLayout_11.addWidget(self.label_29, 1, 0, 1, 1) - self.le_spec_roi = QtWidgets.QLabel(self.groupBox_5) - self.le_spec_roi.setStyleSheet("color: rgb(255, 0,0);") - self.le_spec_roi.setObjectName("le_spec_roi") - self.gridLayout_11.addWidget(self.le_spec_roi, 0, 1, 1, 1) - self.gridLayout_19.addWidget(self.groupBox_5, 0, 1, 1, 1) - self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox_8) - self.groupBox_4.setObjectName("groupBox_4") - self.gridLayout_12 = QtWidgets.QGridLayout(self.groupBox_4) - self.gridLayout_12.setObjectName("gridLayout_12") - self.label_4 = QtWidgets.QLabel(self.groupBox_4) - self.label_4.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_4.setObjectName("label_4") - self.gridLayout_12.addWidget(self.label_4, 0, 0, 1, 1) - self.le_roi = QtWidgets.QLabel(self.groupBox_4) - self.le_roi.setStyleSheet("color: rgb(255, 0,0);") - self.le_roi.setObjectName("le_roi") - self.gridLayout_12.addWidget(self.le_roi, 0, 1, 1, 1) - self.label_28 = QtWidgets.QLabel(self.groupBox_4) - self.label_28.setStyleSheet("") - self.label_28.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_28.setObjectName("label_28") - self.gridLayout_12.addWidget(self.label_28, 1, 0, 1, 1) - self.le_roi_size = QtWidgets.QLabel(self.groupBox_4) - self.le_roi_size.setStyleSheet("color: rgb(255, 0, 0);") - self.le_roi_size.setObjectName("le_roi_size") - self.gridLayout_12.addWidget(self.le_roi_size, 1, 1, 1, 1) - self.gridLayout_19.addWidget(self.groupBox_4, 0, 0, 1, 1) - self.gridLayout_23.addWidget(self.groupBox_8, 0, 0, 1, 1) - self.groupBox_3 = QtWidgets.QGroupBox(self.frame_5) - self.groupBox_3.setObjectName("groupBox_3") - self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_3) - self.gridLayout_6.setObjectName("gridLayout_6") - self.rb_math_roi_img = QtWidgets.QRadioButton(self.groupBox_3) - self.rb_math_roi_img.setLayoutDirection(QtCore.Qt.LeftToRight) - self.rb_math_roi_img.setObjectName("rb_math_roi_img") - self.gridLayout_6.addWidget(self.rb_math_roi_img, 0, 0, 1, 1) - self.pb_reset_roi_2 = QtWidgets.QPushButton(self.groupBox_3) - self.pb_reset_roi_2.setStyleSheet("background-color: rgb(175, 236, 255);") - self.pb_reset_roi_2.setObjectName("pb_reset_roi_2") - self.gridLayout_6.addWidget(self.pb_reset_roi_2, 0, 1, 1, 1) - self.label_14 = QtWidgets.QLabel(self.groupBox_3) - self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_14.setObjectName("label_14") - self.gridLayout_6.addWidget(self.label_14, 1, 0, 1, 1) - self.cb_img_roi_action = QtWidgets.QComboBox(self.groupBox_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_img_roi_action.sizePolicy().hasHeightForWidth()) - self.cb_img_roi_action.setSizePolicy(sizePolicy) - self.cb_img_roi_action.setObjectName("cb_img_roi_action") - self.cb_img_roi_action.addItem("") - self.cb_img_roi_action.addItem("") - self.cb_img_roi_action.addItem("") - self.cb_img_roi_action.addItem("") - self.gridLayout_6.addWidget(self.cb_img_roi_action, 1, 1, 1, 1) - self.gridLayout_23.addWidget(self.groupBox_3, 3, 0, 1, 1) - self.groupBox_7 = QtWidgets.QGroupBox(self.frame_5) - self.groupBox_7.setObjectName("groupBox_7") - self.gridLayout_14 = QtWidgets.QGridLayout(self.groupBox_7) - self.gridLayout_14.setObjectName("gridLayout_14") - self.rb_math_roi = QtWidgets.QRadioButton(self.groupBox_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rb_math_roi.sizePolicy().hasHeightForWidth()) - self.rb_math_roi.setSizePolicy(sizePolicy) - self.rb_math_roi.setCheckable(True) - self.rb_math_roi.setChecked(False) - self.rb_math_roi.setObjectName("rb_math_roi") - self.gridLayout_14.addWidget(self.rb_math_roi, 0, 0, 1, 2) - self.label_13 = QtWidgets.QLabel(self.groupBox_7) - self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_13.setObjectName("label_13") - self.gridLayout_14.addWidget(self.label_13, 1, 0, 1, 1) - self.cb_roi_operation = QtWidgets.QComboBox(self.groupBox_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cb_roi_operation.sizePolicy().hasHeightForWidth()) - self.cb_roi_operation.setSizePolicy(sizePolicy) - self.cb_roi_operation.setObjectName("cb_roi_operation") - self.cb_roi_operation.addItem("") - self.cb_roi_operation.addItem("") - self.cb_roi_operation.addItem("") - self.cb_roi_operation.addItem("") - self.gridLayout_14.addWidget(self.cb_roi_operation, 1, 1, 1, 1) - self.gridLayout_23.addWidget(self.groupBox_7, 10, 0, 1, 1) - self.pb_save_disp_img = QtWidgets.QPushButton(self.frame_5) - self.pb_save_disp_img.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);") - self.pb_save_disp_img.setObjectName("pb_save_disp_img") - self.gridLayout_23.addWidget(self.pb_save_disp_img, 13, 0, 1, 1) - spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_23.addItem(spacerItem4, 4, 0, 1, 1) - self.pb_save_disp_spec = QtWidgets.QPushButton(self.frame_5) - self.pb_save_disp_spec.setStyleSheet("background-color: rgb(175, 236, 255);\n" -"color: rgb(255, 0, 127);") - self.pb_save_disp_spec.setObjectName("pb_save_disp_spec") - self.gridLayout_23.addWidget(self.pb_save_disp_spec, 14, 0, 1, 1) - spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_23.addItem(spacerItem5, 12, 0, 1, 1) - self.groupBox_2 = QtWidgets.QGroupBox(self.frame_5) - self.groupBox_2.setObjectName("groupBox_2") - self.gridLayout_22 = QtWidgets.QGridLayout(self.groupBox_2) - self.gridLayout_22.setObjectName("gridLayout_22") - self.verticalLayout = QtWidgets.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") - self.rb_poly_roi = QtWidgets.QRadioButton(self.groupBox_2) - self.rb_poly_roi.setChecked(True) - self.rb_poly_roi.setObjectName("rb_poly_roi") - self.verticalLayout.addWidget(self.rb_poly_roi) - self.rb_rect_roi = QtWidgets.QRadioButton(self.groupBox_2) - self.rb_rect_roi.setObjectName("rb_rect_roi") - self.verticalLayout.addWidget(self.rb_rect_roi) - self.gridLayout_22.addLayout(self.verticalLayout, 0, 0, 1, 2) - self.verticalLayout_2 = QtWidgets.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.rb_elli_roi = QtWidgets.QRadioButton(self.groupBox_2) - self.rb_elli_roi.setObjectName("rb_elli_roi") - self.verticalLayout_2.addWidget(self.rb_elli_roi) - self.rb_circle_roi = QtWidgets.QRadioButton(self.groupBox_2) - self.rb_circle_roi.setObjectName("rb_circle_roi") - self.verticalLayout_2.addWidget(self.rb_circle_roi) - self.gridLayout_22.addLayout(self.verticalLayout_2, 0, 2, 1, 1) - self.rb_line_roi = QtWidgets.QRadioButton(self.groupBox_2) - self.rb_line_roi.setObjectName("rb_line_roi") - self.gridLayout_22.addWidget(self.rb_line_roi, 1, 0, 1, 1) - self.gridLayout_23.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_23.addItem(spacerItem6, 2, 0, 1, 1) - self.gridLayout_29.addWidget(self.frame_5, 0, 2, 2, 1) - self.image_view = ImageView(self.groupBox_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.image_view.sizePolicy().hasHeightForWidth()) - self.image_view.setSizePolicy(sizePolicy) - self.image_view.setObjectName("image_view") - self.gridLayout_29.addWidget(self.image_view, 0, 1, 1, 1) - self.spectrum_view = PlotWidget(self.groupBox_6) - self.spectrum_view.setObjectName("spectrum_view") - self.gridLayout_29.addWidget(self.spectrum_view, 1, 1, 1, 1) - self.gridLayout_16.addWidget(self.groupBox_6, 0, 1, 1, 1) - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1246, 29)) - self.menubar.setObjectName("menubar") - self.menuManual = QtWidgets.QMenu(self.menubar) - self.menuManual.setObjectName("menuManual") - self.menuFile = QtWidgets.QMenu(self.menubar) - self.menuFile.setObjectName("menuFile") - self.menuMask = QtWidgets.QMenu(self.menubar) - self.menuMask.setObjectName("menuMask") - MainWindow.setMenuBar(self.menubar) - self.statusbar_main = QtWidgets.QStatusBar(MainWindow) - self.statusbar_main.setObjectName("statusbar_main") - MainWindow.setStatusBar(self.statusbar_main) - self.actionOpen_PDF = QtWidgets.QAction(MainWindow) - self.actionOpen_PDF.setObjectName("actionOpen_PDF") - self.actionOpen_in_GitHub = QtWidgets.QAction(MainWindow) - self.actionOpen_in_GitHub.setObjectName("actionOpen_in_GitHub") - self.actionOpen_Image_Data = QtWidgets.QAction(MainWindow) - self.actionOpen_Image_Data.setEnabled(True) - self.actionOpen_Image_Data.setObjectName("actionOpen_Image_Data") - self.actionClose = QtWidgets.QAction(MainWindow) - self.actionClose.setObjectName("actionClose") - self.actionExit = QtWidgets.QAction(MainWindow) - self.actionExit.setObjectName("actionExit") - self.actionSave_as = QtWidgets.QAction(MainWindow) - self.actionSave_as.setObjectName("actionSave_as") - self.actionOpen_PyXRF = QtWidgets.QAction(MainWindow) - self.actionOpen_PyXRF.setObjectName("actionOpen_PyXRF") - self.actionOpen_Image_J = QtWidgets.QAction(MainWindow) - self.actionOpen_Image_J.setObjectName("actionOpen_Image_J") - self.actionOpen_TomViz = QtWidgets.QAction(MainWindow) - self.actionOpen_TomViz.setObjectName("actionOpen_TomViz") - self.actionOpen_Mantis = QtWidgets.QAction(MainWindow) - self.actionOpen_Mantis.setObjectName("actionOpen_Mantis") - self.actionOpen_Athena = QtWidgets.QAction(MainWindow) - self.actionOpen_Athena.setObjectName("actionOpen_Athena") - self.actionDataBroker = QtWidgets.QAction(MainWindow) - self.actionDataBroker.setObjectName("actionDataBroker") - self.actionOpen_HXN_DB = QtWidgets.QAction(MainWindow) - self.actionOpen_HXN_DB.setObjectName("actionOpen_HXN_DB") - self.actionLoad_Energy = QtWidgets.QAction(MainWindow) - self.actionLoad_Energy.setObjectName("actionLoad_Energy") - self.actionOpen_Multiple_Files = QtWidgets.QAction(MainWindow) - self.actionOpen_Multiple_Files.setObjectName("actionOpen_Multiple_Files") - self.actionOpen_Mask_Gen = QtWidgets.QAction(MainWindow) - self.actionOpen_Mask_Gen.setObjectName("actionOpen_Mask_Gen") - self.actionCreate_elist_from_log = QtWidgets.QAction(MainWindow) - self.actionCreate_elist_from_log.setObjectName("actionCreate_elist_from_log") - self.actionSave_Energy_List = QtWidgets.QAction(MainWindow) - self.actionSave_Energy_List.setObjectName("actionSave_Energy_List") - self.actionAlign_Stack = QtWidgets.QAction(MainWindow) - self.actionAlign_Stack.setObjectName("actionAlign_Stack") - self.menuManual.addSeparator() - self.menuManual.addAction(self.actionOpen_in_GitHub) - self.menuFile.addAction(self.actionOpen_Image_Data) - self.menuFile.addAction(self.actionOpen_Multiple_Files) - self.menuFile.addAction(self.actionLoad_Energy) - self.menuFile.addAction(self.actionSave_Energy_List) - self.menuFile.addAction(self.actionSave_as) - self.menuFile.addAction(self.actionExit) - self.menuMask.addAction(self.actionOpen_Mask_Gen) - self.menuMask.addAction(self.actionAlign_Stack) - self.menubar.addAction(self.menuFile.menuAction()) - self.menubar.addAction(self.menuMask.menuAction()) - self.menubar.addAction(self.menuManual.menuAction()) - - self.retranslateUi(MainWindow) - self.toolBox.setCurrentIndex(1) - self.toolBox_2.setCurrentIndex(2) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "NSLS-II MIDAS")) - self.pb_reset_img.setText(_translate("MainWindow", "Reset Image")) - self.label_5.setText(_translate("MainWindow", "to")) - self.label_9.setText(_translate("MainWindow", "to")) - self.sb_yrange2.setSuffix(_translate("MainWindow", " px")) - self.sb_xrange1.setSuffix(_translate("MainWindow", " px")) - self.sb_xrange2.setSuffix(_translate("MainWindow", " px")) - self.label_22.setText(_translate("MainWindow", "to")) - self.label_19.setText(_translate("MainWindow", "X Dimension")) - self.sb_yrange1.setSuffix(_translate("MainWindow", " px")) - self.label_15.setText(_translate("MainWindow", "Y Dimension")) - self.label_20.setText(_translate("MainWindow", "Stack Range ")) - self.pb_crop.setToolTip(_translate("MainWindow", "Adjust the dimensions of the image")) - self.pb_crop.setText(_translate("MainWindow", "Update")) - self.cb_rebin.setText(_translate("MainWindow", "rebin")) - self.cb_upscale.setText(_translate("MainWindow", "Upscale")) - self.cb_remove_edges.setToolTip(_translate("MainWindow", "Removes one column/row from all four edges")) - self.cb_remove_edges.setText(_translate("MainWindow", "Remove Edges")) - self.toolBox.setItemText(self.toolBox.indexOf(self.page), _translate("MainWindow", "Adjust Image Dimensions")) - self.groupBox_9.setTitle(_translate("MainWindow", "Transform")) - self.cb_log.setToolTip(_translate("MainWindow", "Covert image dat to log values.")) - self.cb_log.setText(_translate("MainWindow", "Log ")) - self.cb_norm.setToolTip(_translate("MainWindow", "Intensity Normalization with the last Frame.")) - self.cb_norm.setText(_translate("MainWindow", "Normalize")) - self.cb_transpose.setToolTip(_translate("MainWindow", "Reverse the arrays. 012 will be 210")) - self.cb_transpose.setText(_translate("MainWindow", "Transpose")) - self.cb_remove_outliers.setText(_translate("MainWindow", "Remove Outliers (NSigma)")) - self.label_nsigma.setText(_translate("MainWindow", "1")) - self.cb_remove_bg.setText(_translate("MainWindow", "Thresholding")) - self.label_bg_threshold.setText(_translate("MainWindow", "5")) - self.cb_smooth.setToolTip(_translate("MainWindow", "uses savgol_filter to smooth data")) - self.cb_smooth.setText(_translate("MainWindow", "Smoothen")) - self.smooth_winow_size.setText(_translate("MainWindow", "Window size")) - self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("MainWindow", "Image Processing")) - self.pb_calc_components.setText(_translate("MainWindow", "Calculate Components")) - self.pb_pca_scree.setText(_translate("MainWindow", "PCA Scree Plot")) - self.label_11.setText(_translate("MainWindow", "Method")) - self.cb_comp_method.setItemText(0, _translate("MainWindow", "PCA")) - self.cb_comp_method.setItemText(1, _translate("MainWindow", "NMF")) - self.cb_comp_method.setItemText(2, _translate("MainWindow", "FastICA")) - self.cb_comp_method.setItemText(3, _translate("MainWindow", "IncrementalPCA")) - self.cb_comp_method.setItemText(4, _translate("MainWindow", "TruncatedSVD")) - self.cb_comp_method.setItemText(5, _translate("MainWindow", "FactorAnalysis")) - self.cb_comp_method.setItemText(6, _translate("MainWindow", "DictionaryLearning")) - self.label.setText(_translate("MainWindow", "Number of Components")) - self.toolBox_2.setItemText(self.toolBox_2.indexOf(self.page_5), _translate("MainWindow", "Component Analysis")) - self.pb_calc_cluster.setText(_translate("MainWindow", "Calculate Clusters")) - self.pb_kmeans_elbow.setText(_translate("MainWindow", "KMeans Variance Plot")) - self.label_10.setText(_translate("MainWindow", "Method")) - self.cb_clust_method.setItemText(0, _translate("MainWindow", "KMeans")) - self.cb_clust_method.setItemText(1, _translate("MainWindow", "MiniBatchKMeans")) - self.cb_clust_method.setItemText(2, _translate("MainWindow", "MeanShift")) - self.cb_clust_method.setItemText(3, _translate("MainWindow", "Spectral Clustering")) - self.cb_clust_method.setItemText(4, _translate("MainWindow", "Correlation-Kmeans")) - self.cb_clust_method.setItemText(5, _translate("MainWindow", "Affinity Propagation")) - self.label_2.setText(_translate("MainWindow", "Number of Clusters")) - self.toolBox_2.setItemText(self.toolBox_2.indexOf(self.page_6), _translate("MainWindow", "Cluster Analysis")) - self.pb_elist_xanes.setText(_translate("MainWindow", "Load Energy List")) - self.cb_kev_flag.setText(_translate("MainWindow", "keV")) - self.pb_ref_xanes.setText(_translate("MainWindow", "Load Ref. Spec.")) - self.pb_plot_refs.setText(_translate("MainWindow", "Plot")) - self.cb_xanes_fitting_method.setItemText(0, _translate("MainWindow", "NNLS")) - self.pb_xanes_fit.setText(_translate("MainWindow", " Fit")) - self.toolBox_2.setItemText(self.toolBox_2.indexOf(self.page_7), _translate("MainWindow", "XANES Fitting")) - self.groupBox_8.setTitle(_translate("MainWindow", "ROI Positions")) - self.groupBox_5.setTitle(_translate("MainWindow", "Spectrum ROI")) - self.le_spec_roi_size.setText(_translate("MainWindow", "roi_size")) - self.label_8.setText(_translate("MainWindow", "Range (eV):")) - self.label_29.setText(_translate("MainWindow", "Size (eV):")) - self.le_spec_roi.setText(_translate("MainWindow", "roix, roiy")) - self.groupBox_4.setTitle(_translate("MainWindow", "Image ROI")) - self.label_4.setText(_translate("MainWindow", "Position:")) - self.le_roi.setText(_translate("MainWindow", "roix, roiy")) - self.label_28.setText(_translate("MainWindow", "Size(pixels):")) - self.le_roi_size.setText(_translate("MainWindow", "roi_size")) - self.groupBox_3.setTitle(_translate("MainWindow", "Image Calculations")) - self.rb_math_roi_img.setText(_translate("MainWindow", "Add Math ROI")) - self.pb_reset_roi_2.setText(_translate("MainWindow", "Reset")) - self.label_14.setText(_translate("MainWindow", "Action:")) - self.cb_img_roi_action.setItemText(0, _translate("MainWindow", "Subtract")) - self.cb_img_roi_action.setItemText(1, _translate("MainWindow", "Divide")) - self.cb_img_roi_action.setItemText(2, _translate("MainWindow", "Add")) - self.cb_img_roi_action.setItemText(3, _translate("MainWindow", "Compare")) - self.groupBox_7.setTitle(_translate("MainWindow", "Spectrum Calculations")) - self.rb_math_roi.setText(_translate("MainWindow", "Add Math ROI")) - self.label_13.setText(_translate("MainWindow", "Action:")) - self.cb_roi_operation.setItemText(0, _translate("MainWindow", "Divide")) - self.cb_roi_operation.setItemText(1, _translate("MainWindow", "Subtract")) - self.cb_roi_operation.setItemText(2, _translate("MainWindow", "Add")) - self.cb_roi_operation.setItemText(3, _translate("MainWindow", "Correlation Plot")) - self.pb_save_disp_img.setText(_translate("MainWindow", "Save Current Image (.tiff)")) - self.pb_save_disp_spec.setText(_translate("MainWindow", "Save Current Spectrum (.txt)")) - self.groupBox_2.setTitle(_translate("MainWindow", "ROI Shape")) - self.rb_poly_roi.setText(_translate("MainWindow", "Polygon (default)")) - self.rb_rect_roi.setText(_translate("MainWindow", "Rectangle")) - self.rb_elli_roi.setText(_translate("MainWindow", "Ellipse")) - self.rb_circle_roi.setText(_translate("MainWindow", "Circle")) - self.rb_line_roi.setText(_translate("MainWindow", "Line")) - self.menuManual.setTitle(_translate("MainWindow", "Help")) - self.menuFile.setTitle(_translate("MainWindow", "File")) - self.menuMask.setTitle(_translate("MainWindow", "Tools")) - self.actionOpen_PDF.setText(_translate("MainWindow", "Open PDF")) - self.actionOpen_in_GitHub.setText(_translate("MainWindow", "Open in GitHub (most updated)")) - self.actionOpen_Image_Data.setText(_translate("MainWindow", "Open Image Data")) - self.actionOpen_Image_Data.setToolTip(_translate("MainWindow", "Support all tiff and specific h5 files")) - self.actionOpen_Image_Data.setShortcut(_translate("MainWindow", "Ctrl+O")) - self.actionClose.setText(_translate("MainWindow", "Close")) - self.actionExit.setText(_translate("MainWindow", "Exit")) - self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Q")) - self.actionSave_as.setText(_translate("MainWindow", "Export Tiff Stack")) - self.actionSave_as.setToolTip(_translate("MainWindow", "Save the displayed/Modified stack as a tiff file")) - self.actionSave_as.setShortcut(_translate("MainWindow", "Ctrl+S")) - self.actionOpen_PyXRF.setText(_translate("MainWindow", "Open PyXRF")) - self.actionOpen_Image_J.setText(_translate("MainWindow", "Open Image J")) - self.actionOpen_TomViz.setText(_translate("MainWindow", "Open TomViz")) - self.actionOpen_Mantis.setText(_translate("MainWindow", "Open Mantis")) - self.actionOpen_Athena.setText(_translate("MainWindow", "Open Athena")) - self.actionDataBroker.setText(_translate("MainWindow", "DataBroker")) - self.actionOpen_HXN_DB.setText(_translate("MainWindow", "Open HXN DB")) - self.actionLoad_Energy.setText(_translate("MainWindow", "Load Energy")) - self.actionLoad_Energy.setToolTip(_translate("MainWindow", "Load list of energies for XANES stack. Supports only .txt fromat")) - self.actionLoad_Energy.setShortcut(_translate("MainWindow", "Ctrl+E")) - self.actionOpen_Multiple_Files.setText(_translate("MainWindow", "Open Multiple Files")) - self.actionOpen_Multiple_Files.setToolTip(_translate("MainWindow", "Create a stack from multiple tiff images of same shape")) - self.actionOpen_Multiple_Files.setShortcut(_translate("MainWindow", "Ctrl+M")) - self.actionOpen_Mask_Gen.setText(_translate("MainWindow", "Open Mask Generator")) - self.actionOpen_Mask_Gen.setToolTip(_translate("MainWindow", "A new window will be opened to creat threshold based masks")) - self.actionCreate_elist_from_log.setText(_translate("MainWindow", "Create elist from log file")) - self.actionSave_Energy_List.setText(_translate("MainWindow", "Save Energy List")) - self.actionAlign_Stack.setText(_translate("MainWindow", "Align Stack")) - self.actionAlign_Stack.setToolTip(_translate("MainWindow", "A new window will be opened to align images in a stack")) -from pyqtgraph import ImageView, PlotWidget - - -if __name__ == "__main__": - import sys - app = QtWidgets.QApplication(sys.argv) - MainWindow = QtWidgets.QMainWindow() - ui = Ui_MainWindow() - ui.setupUi(MainWindow) - MainWindow.show() - sys.exit(app.exec_()) diff --git a/xmidas/uis/midasMainwindow.ui b/xmidas/uis/midasMainwindow.ui index eb98ef8..dcd166c 100644 --- a/xmidas/uis/midasMainwindow.ui +++ b/xmidas/uis/midasMainwindow.ui @@ -1,2938 +1,3034 @@ - - - Ajith - MainWindow - - - true - - - - 0 - 0 - 1232 - 931 - - - - - 0 - 0 - - - - NSLS-II MIDAS - - - - pancake.icopancake.ico - - - font: 10pt "Segoe UI"; - - - - - true - - - - 0 - 0 - - - - QWidget { -font: 10pt "Segoe UI"; -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 5,0); -font: 10pt "Segoe UI"; -} - -QLabel { -font: 10pt "Segoe UI"; -} - -QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - - - - 0 - 0 - - - - - - - 2 - - - - - 0 - 0 - 368 - 325 - - - - Adjust Image Dimensions - - - - - - - 0 - 0 - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - 0 - - - 500 - - - 0 - - - 10 - - - - - - - - 0 - 0 - - - - 1 - - - 5000 - - - 1200 - - - 10 - - - - - - - - 0 - 0 - - - - false - - - px - - - 1 - - - 10000 - - - 100 - - - - - - - - 0 - 0 - - - - false - - - px - - - 1 - - - 10000 - - - 100 - - - - - - - - 0 - 0 - - - - false - - - px - - - 0 - - - 10000 - - - 0 - - - - - - - - 0 - 0 - - - - Y Dimension - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - false - - - px - - - 0 - - - 10000 - - - 0 - - - - - - - - 0 - 0 - - - - X Dimension - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Stack Range - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - Adjust the dimensions of the image - - - - - - Update - - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - 0 - 0 - - - - Removes one column/row from all four edges - - - Remove Edges - - - - - - - - - true - - - - 0 - 0 - - - - rebin - - - - - - - true - - - - 0 - 0 - - - - Upscale - - - - - - - - - Ratio - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - 0 - 0 - - - - font: 10pt "MS Shell Dlg 2"; - - - 2 - - - 16 - - - 2 - - - 2 - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - 0 - 300 - 392 - - - - Image Processing - - - - - - - - - reverses the image axes (ZXY) to (YXZ) - - - Transpose - - - - - - - swaps 2nd and 3rd axes (ZXY) to (ZYX) - - - Swap XY Axes - - - - - - - - - - - - 0 - 0 - - - - Covert image dat to log values. - - - - - - Log - - - false - - - - - - - - 0 - 0 - - - - Intensity Normalization with the last Frame. - - - Normalize to Max - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - Remove Outliers (NSigma) - - - - - - - false - - - - - - 200 - - - 3 - - - Qt::Horizontal - - - - - - - 1 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - Thresholding - - - - - - - - - - - false - - - 100 - - - 5 - - - 5 - - - 5 - - - Qt::Horizontal - - - - - - - 5 - - - - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - uses savgol_filter to smooth data - - - Smoothen - - - - - - - - - false - - - 3 - - - 12 - - - 2 - - - 2 - - - 3 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 2 - - - - - - - Window size - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - - - 0 - 0 - 379 - 309 - - - - Alignment - - - - - - - - - - - Transformations: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - TRANSLATION - - - - - RIGID_BODY - - - - - SCALED_ROTATION - - - - - AFFINE - - - - - BILINEAR - - - - - - - - - - - - Reference: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - previous - - - - - mean - - - - - first - - - - - - - - - - - - - 0 - 0 - - - - Iterative Mode - - - - - - - - - Max Iter. - - - - - - - - 0 - 0 - - - - 2 - - - 24 - - - - - - - - - - - - 0 - 0 - - - - Align - - - - - - - - - - 0 - 0 - - - - Load Reference Stack - - - - - - - No Ref. Available - - - true - - - - - - - - - - 0 - 0 - - - - Save Transformation File - - - - - - - Use - - - - - - - - 0 - 0 - - - - Load Transformation File - - - - - - - - - - - - - - - 0 - 0 - - - - - - - Reset Image - - - - - - - true - - - 2 - - - - - 0 - 0 - 379 - 321 - - - - Component Analysis - - - - - - - - - 0 - 0 - - - - - - - Calculate Components - - - - - - - - 0 - 0 - - - - - - - PCA Scree Plot - - - - - - - - - Method - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - PCA - - - - - NMF - - - - - FastICA - - - - - IncrementalPCA - - - - - TruncatedSVD - - - - - FactorAnalysis - - - - - DictionaryLearning - - - - - - - - - - - - Number of Components - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 1 - - - 4 - - - - - - - - - - - - - 0 - 0 - 379 - 321 - - - - Cluster Analysis - - - - - - - - - 0 - 0 - - - - - - - Calculate Clusters - - - - - - - - 0 - 0 - - - - - - - KMeans Variance Plot - - - - - - - Method - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - KMeans - - - - - MiniBatchKMeans - - - - - MeanShift - - - - - Spectral Clustering - - - - - Correlation-Kmeans - - - - - Affinity Propagation - - - - - - - - Number of Clusters - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 1 - - - 4 - - - - - - - - - - - 0 - 0 - 379 - 205 - - - - XANES Fitting - - - - - - - 11 - - - 11 - - - - - - - - 0 - 0 - - - - - - - Load Energy List - - - - - - - keV - - - - - - - - - - - - 0 - 0 - - - - - - - Load Ref. Spec. - - - - - - - - - - Plot - - - - - - - - - true - - - - 0 - 0 - - - - - - - Fit - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - QLayout::SetMaximumSize - - - - - 0 - - - - Live - - - - - - - 0 - 0 - - - - Send to Plot Collection - - - - - - - - 0 - 0 - - - - - - - Save - - - - - - - - 0 - 0 - - - - - - - - - Normalized - - - - - - Save - - - - - - - Clear - - - - - - - Norm. to Collector - - - - - - - - 0 - 0 - - - - - - - - - Collection - - - - - - - 0 - 0 - - - - - - - - Clear - - - - - - - Save - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - QLayout::SetDefaultConstraint - - - 11 - - - 11 - - - - - - 0 - 0 - - - - ROI Shape - - - - - - - 0 - 0 - - - - Rectangle - - - true - - - - - - - - 0 - 0 - - - - Ellipse - - - - - - - - 0 - 0 - - - - Polygon - - - false - - - - - - - - 0 - 0 - - - - Circle - - - - - - - - 0 - 0 - - - - Line - - - - - - - - 0 - 0 - - - - Zoom to ROI - - - - - - - - - - font: 9pt "Segoe UI"; - - - ROI Positions - - - - - - Spectrum ROI - - - - - - Range (eV): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - color: rgb(255, 0,0); - - - roi_size - - - - - - - color: rgb(255, 0,0); - - - roix, roiy - - - - - - - - - - Size (eV): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Image ROI - - - - - - color: rgb(255, 0,0); - - - roix, roiy - - - - - - - Position: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Size(pixels): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - color: rgb(255, 0, 0); - - - roi_size - - - - - - - - - - - - - - 0 - 0 - - - - 1 - - - - - 0 - 0 - 361 - 193 - - - - Image Calculations - - - - - - - - - - - - - - - Add ROI_2 - - - - - - - Calculation: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - Subtract - - - - - Divide - - - - - Add - - - - - Compare - - - - - - - - Apply - - - - - - - - - - - - 0 - 0 - 361 - 231 - - - - Spectrum Calculations - - - - - - - - - - - - - 0 - 0 - - - - Add ROI 2 - - - true - - - false - - - false - - - - - - - - - - 0 - 0 - - - - Calculation - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - Divide - - - - - Subtract - - - - - Add - - - - - Correlation Plot - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Apply - - - - - - - Use ROI for Correlations - - - - - - - - - - - - 0 - 0 - 386 - 273 - - - - XANES Normalization - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - 1 - - - 5 - - - - - - - - 0 - 0 - - - - Eo - - - - - - - - 0 - 0 - - - - eV - - - 1000.000000000000000 - - - 20000.000000000000000 - - - 7125.000000000000000 - - - - - - - - 0 - 0 - - - - calculate the energy point with maximum derivative - - - Auto Eo - - - - - - - - 0 - 0 - - - - eV - - - 2 - - - -500.000000000000000 - - - 500.000000000000000 - - - 1.000000000000000 - - - -50.000000000000000 - - - - - - - - 0 - 0 - - - - Pre-edge - - - - - - - - 0 - 0 - - - - eV - - - -500.000000000000000 - - - 500.000000000000000 - - - -10.000000000000000 - - - - - - - - 0 - 0 - - - - Post-edge - - - - - - - - 0 - 0 - - - - to - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Norm. Order - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - eV - - - 0.000000000000000 - - - 1000.000000000000000 - - - 25.000000000000000 - - - - - - - - 0 - 0 - - - - <html><head/><body><p> For mbak algorithm only, must be in <span style=" font-weight:600; font-style:italic;">element&lt;space&gt;edge</span> format</p></body></html> - - - Fe K - - - - - - - - 0 - 0 - - - - Element: - - - - - - - - 0 - 0 - - - - eV - - - 0.000000000000000 - - - 1500.000000000000000 - - - 75.000000000000000 - - - - - - - Initial Guess - - - - - - - - 0 - 0 - - - - Apply to Spectrum - - - - - - - Apply to Stack - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - 0 - 1232 - 29 - - - - - - - - Help - - - - - - - File - - - - - - - - - - - - true - - - Accessories - - - - - - - View - - - - Plot Background - - - - - - - - - - Window Background - - - - - - - - Change_Plot_Line_Width - - - - - - - - - - - - - - - - - Spectrum - - - - - - - - - Image - - - - - - - - - - - Batch - - - - - - - - - - - - - - - - - 0 - 0 - - - - toolBar - - - TopToolBarArea - - - false - - - - - - - - Open PDF - - - - - Open in GitHub (most updated) - - - - - true - - - Open Image Data - - - Support all tiff and specific h5 files - - - Ctrl+O - - - - - Close - - - - - Exit - - - Ctrl+Q - - - - - false - - - Export Tiff Stack - - - Save the displayed/Modified stack as a tiff file - - - Ctrl+S - - - - - Open PyXRF - - - - - Open Image J - - - - - Open TomViz - - - - - Open Mantis - - - - - Open Athena - - - - - DataBroker - - - - - Open HXN DB - - - - - false - - - Load Energy - - - Load list of energies for XANES stack. Supports only .txt fromat - - - Ctrl+E - - - - - Create a Virtual Stack - - - Create a stack from multiple tiff images of same shape - - - Ctrl+M - - - - - true - - - Open Mask Generator - - - A new window will be opened to creat threshold based masks - - - - - Create elist from log file - - - - - false - - - Export Energy List - - - - - true - - - MultiColorView - - - A new window will be opened to align images in a stack - - - - - White - - - - - true - - - true - - - Black - - - - - Red - - - - - Yellow - - - - - Blue - - - - - false - - - false - - - Dark Mode - - - - - Black - - - - - false - - - false - - - Default - - - - - Vivid - - - - - Export Sum Image (3D to 2D) - - - - - Subtract ROI as Background - - - - - Export Norm. Params - - - - - 2 - - - - - 4 - - - - - 6 - - - - - 10 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 8 - - - - - 10 - - - - - 2 - - - - - Import Norm. Params - - - - - 1 - - - - - Save Sum Spectrum - - - - - Save Mean Spectrum - - - - - Save Current Image as Mask - - - - - Stack to RGBCMY Image - - - - - Apply Current Crop to All images - - - all images in the folder will be cropped in reference to current - - - - - - - - - - - Save Current State - - - - - Normalize with another Stack - - - - - Stack Info - - - Diaplays details of the stack - - - - - Export Image - - - Saves displayed 2D image frame - - - - - - .. - - - Export Stack - - - Save current stack as is - - - false - - - - - Export Mean Image (3D to 2D) - - - - - Plot All Possible Image Correlations - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + Ajith + MainWindow + + + true + + + + 0 + 0 + 1287 + 842 + + + + + 0 + 0 + + + + NSLS-II MIDAS + + + + pancake.icopancake.ico + + + font: 10pt "Segoe UI"; + + + + + true + + + + 0 + 0 + + + + QWidget { +font: 10pt "Segoe UI"; +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 5,0); +font: 10pt "Segoe UI"; +} + +QLabel { +font: 10pt "Segoe UI"; +} + +QSlider::groove:horizontal { +border: 1px solid #bbb; +background: white; +height: 10px; +border-radius: 4px; +} + +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #66e, stop: 1 #bbf); +background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, + stop: 0 #bbf, stop: 1 #55f); +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::add-page:horizontal { +background: #fff; +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::handle:horizontal { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #eee, stop:1 #ccc); +border: 1px solid #777; +width: 13px; +margin-top: -2px; +margin-bottom: -2px; +border-radius: 4px; +} + +QSlider::handle:horizontal:hover { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #fff, stop:1 #ddd); +border: 1px solid #444; +border-radius: 2px; +} + +QSlider::sub-page:horizontal:disabled { +background: #bbb; +border-color: #999; +} + +QSlider::add-page:horizontal:disabled { +background: #eee; +border-color: #999; +} + +QSlider::handle:horizontal:disabled { +background: #eee; +border: 1px solid #aaa; +border-radius: 4px; +} + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 0 + + + + + + + 0 + + + + + 0 + 0 + 302 + 288 + + + + Adjust Image Dimensions + + + + + + + 0 + 0 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + 0 + + + 500 + + + 0 + + + 10 + + + + + + + + 0 + 0 + + + + 1 + + + 5000 + + + 1200 + + + 10 + + + + + + + + 0 + 0 + + + + false + + + px + + + 1 + + + 10000 + + + 100 + + + + + + + + 0 + 0 + + + + false + + + px + + + 1 + + + 10000 + + + 100 + + + + + + + + 0 + 0 + + + + false + + + px + + + 0 + + + 10000 + + + 0 + + + + + + + + 0 + 0 + + + + Y Dimension + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + false + + + px + + + 0 + + + 10000 + + + 0 + + + + + + + + 0 + 0 + + + + X Dimension + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Stack Range + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + Adjust the dimensions of the image + + + + + + Update + + + + + + + Apply crop to all tiffs in directory + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + 0 + 0 + + + + Removes one column/row from all four edges + + + Remove Edges + + + + + + + + + true + + + + 0 + 0 + + + + rebin + + + + + + + true + + + + 0 + 0 + + + + Upscale + + + + + + + + + Ratio + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + 0 + 0 + + + + font: 10pt "MS Shell Dlg 2"; + + + 2 + + + 16 + + + 2 + + + 2 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + 233 + 365 + + + + Image Processing + + + + + + + + + reverses the image axes (ZXY) to (YXZ) + + + Transpose + + + + + + + swaps 2nd and 3rd axes (ZXY) to (ZYX) + + + Swap XY Axes + + + + + + + + + + + + 0 + 0 + + + + Covert image dat to log values. + + + + + + Log + + + false + + + + + + + + 0 + 0 + + + + Intensity Normalization with the last Frame. + + + Normalize to Max + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + Remove Outliers (NSigma) + + + + + + + false + + + + + + 200 + + + 3 + + + Qt::Horizontal + + + + + + + 1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + Thresholding + + + + + + + + + + + false + + + 100 + + + 5 + + + 5 + + + 5 + + + Qt::Horizontal + + + + + + + 5 + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + uses savgol_filter to smooth data + + + Smoothen + + + + + + + + + false + + + 3 + + + 12 + + + 2 + + + 2 + + + 3 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 2 + + + + + + + Window size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + 0 + 0 + 286 + 247 + + + + Alignment + + + + + + + + + + + Transformations: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + TRANSLATION + + + + + RIGID_BODY + + + + + SCALED_ROTATION + + + + + AFFINE + + + + + BILINEAR + + + + + + + + + + + + Reference: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + previous + + + + + mean + + + + + first + + + + + + + + + + + + + 0 + 0 + + + + Iterative Mode + + + + + + + + + Max Iter. + + + + + + + + 0 + 0 + + + + 2 + + + 24 + + + + + + + + + + + + 0 + 0 + + + + Align + + + + + + + + + + 0 + 0 + + + + Load Reference Stack + + + + + + + No Ref. Available + + + true + + + + + + + + + + 0 + 0 + + + + Save Transformation File + + + + + + + Use + + + + + + + + 0 + 0 + + + + Load Transformation File + + + + + + + + + + + + + + + 0 + 0 + + + + + + + Reset Image + + + + + + + true + + + 1 + + + + + 0 + 0 + 302 + 205 + + + + Component Analysis + + + + + + + + + 0 + 0 + + + + + + + Calculate Components + + + + + + + + 0 + 0 + + + + + + + PCA Scree Plot + + + + + + + + + Method + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + PCA + + + + + NMF + + + + + FastICA + + + + + IncrementalPCA + + + + + TruncatedSVD + + + + + FactorAnalysis + + + + + DictionaryLearning + + + + + + + + + + + + Number of Components + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 4 + + + + + + + + + + + + + 0 + 0 + 302 + 205 + + + + Cluster Analysis + + + + + + + + + 0 + 0 + + + + + + + Calculate Clusters + + + + + + + + 0 + 0 + + + + + + + KMeans Variance Plot + + + + + + + Method + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + KMeans + + + + + MiniBatchKMeans + + + + + MeanShift + + + + + Spectral Clustering + + + + + Correlation-Kmeans + + + + + Affinity Propagation + + + + + + + + Number of Clusters + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 4 + + + + + + + + + + + 0 + 0 + 302 + 205 + + + + XANES Fitting + + + + + + + 11 + + + 11 + + + + + + + + 0 + 0 + + + + + + + Load Energy List + + + + + + + keV + + + + + + + + + + + + 0 + 0 + + + + + + + Load Ref. Spec. + + + + + + + + + + Plot + + + + + + + + + true + + + + 0 + 0 + + + + + + + Fit + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + QLayout::SetMaximumSize + + + + + 0 + + + + Live + + + + + + + 0 + 0 + + + + Send to Plot Collection + + + + + + + + 0 + 0 + + + + + + + Save + + + + + + + + 0 + 0 + + + + + + + + + Normalized + + + + + + + 0 + 0 + + + + + + + + Norm. to Collector + + + + + + + Save + + + + + + + Clear + + + + + + + + Collection + + + + + + + 0 + 0 + + + + + + + + Clear + + + + + + + Save + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + font: 9pt "Segoe UI"; + + + ROI Positions + + + + + + Spectrum ROI + + + + + + Range (eV): + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + color: rgb(255, 0,0); + + + roi_size + + + + + + + color: rgb(255, 0,0); + + + roix, roiy + + + + + + + + + + Size (eV): + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Image ROI + + + + + + Position: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + color: rgb(255, 0,0); + + + roix, roiy + + + + + + + + + + Size(pixels): + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + color: rgb(255, 0, 0); + + + roi_size + + + + + + + + + + + + + + 0 + 0 + + + + ROI Shape + + + + + + + 0 + 0 + + + + Ellipse + + + + + + + + 0 + 0 + + + + Rectangle + + + true + + + + + + + + 0 + 0 + + + + Polygon + + + false + + + + + + + + 0 + 0 + + + + Circle + + + + + + + + 0 + 0 + + + + Zoom to ROI + + + + + + + + 0 + 0 + + + + Line + + + + + + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + 2 + + + + + 0 + 0 + 305 + 384 + + + + Image Calculations + + + + + + + + + + + + + + + Add ROI_2 + + + + + + + Calculation: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + Subtract + + + + + Divide + + + + + Add + + + + + Compare + + + + + + + + Apply + + + + + + + + + + + + 0 + 0 + 323 + 384 + + + + Spectrum Calculations + + + + + + + + + + + + + 0 + 0 + + + + Add ROI 2 + + + true + + + false + + + false + + + + + + + + + + 0 + 0 + + + + Calculation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + Divide + + + + + Subtract + + + + + Add + + + + + Correlation Plot + + + + + + + + + + Use ROI for Correlations + + + + + + + Apply + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + 365 + 384 + + + + XANES Normalization + + + + + + + + + + + + + + + 0 + 0 + + + + calculate the energy point with maximum derivative + + + Find Eo + + + + + + + + 0 + 0 + + + + Initial Guess + + + + + + + + + + + + 0 + 0 + + + + Element_Edge: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Pre-edge + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Post-edge + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p> For mbak algorithm only, must be in <span style=" font-weight:600; font-style:italic;">element&lt;space&gt;edge</span> format</p></body></html> + + + Fe_K + + + + + + + + 0 + 0 + + + + eV + + + 2 + + + -500.000000000000000 + + + 500.000000000000000 + + + 1.000000000000000 + + + -50.000000000000000 + + + + + + + + 0 + 0 + + + + eV + + + 0.000000000000000 + + + 1000.000000000000000 + + + 25.000000000000000 + + + + + + + + + + + + 0 + 0 + + + + Eo: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + to + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 0 + + + + eV + + + 1000.000000000000000 + + + 20000.000000000000000 + + + 7125.000000000000000 + + + + + + + + 0 + 0 + + + + eV + + + -500.000000000000000 + + + 500.000000000000000 + + + -10.000000000000000 + + + + + + + + 0 + 0 + + + + eV + + + 0.000000000000000 + + + 1500.000000000000000 + + + 75.000000000000000 + + + + + + + + + + + + + + + + 0 + 0 + + + + Norm. Order + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + 1 + + + 5 + + + + + + + + + + 0 + 0 + + + + Use Flattened + + + + + + + + 0 + 0 + + + + Apply to Spectrum + + + + + + + + 0 + 0 + + + + Apply to Stack + + + + + + + + 0 + 0 + + + + Ignore Post Edge (Stack Only) + + + + + + + MBAK Algorithm + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1287 + 24 + + + + + + + + Help + + + + + + + File + + + + + + + + + + + + true + + + Accessories + + + + + + + View + + + + Plot Background + + + + + + + + + + Window Background + + + + + + + + Change_Plot_Line_Width + + + + + + + + + + + + + + + + + Spectrum + + + + + + + + + Image + + + + + + + + + + + + Batch + + + + + + + + + + + + + + + + + 0 + 0 + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + Open PDF + + + + + Open in GitHub (most updated) + + + + + true + + + Open Image Data + + + Support all tiff and specific h5 files + + + Ctrl+O + + + + + Close + + + + + Exit + + + Ctrl+Q + + + + + false + + + Export Tiff Stack + + + Save the displayed/Modified stack as a tiff file + + + Ctrl+S + + + + + Open PyXRF + + + + + Open Image J + + + + + Open TomViz + + + + + Open Mantis + + + + + Open Athena + + + + + DataBroker + + + + + Open HXN DB + + + + + false + + + Load Energy + + + Load list of energies for XANES stack. Supports only .txt fromat + + + Ctrl+E + + + + + Create a Virtual Stack + + + Create a stack from multiple tiff images of same shape + + + Ctrl+M + + + + + true + + + Open Mask Generator + + + A new window will be opened to creat threshold based masks + + + + + Create elist from log file + + + + + false + + + Export Energy List + + + + + true + + + MultiColorView + + + A new window will be opened to align images in a stack + + + + + White + + + + + true + + + true + + + Black + + + + + Red + + + + + Yellow + + + + + Blue + + + + + false + + + false + + + Dark Mode + + + + + Black + + + + + false + + + false + + + Default + + + + + Vivid + + + + + Export Sum Image (XYZ to sum(XY)) + + + + + Subtract ROI as Background + + + + + Export Norm. Params + + + + + 2 + + + + + 4 + + + + + 6 + + + + + 10 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 8 + + + + + 10 + + + + + 2 + + + + + Import Norm. Params + + + + + 1 + + + + + Save Sum Spectrum + + + + + Save Mean Spectrum + + + + + Save Current Image as Mask + + + + + Stack to RGBCMY Image + + + + + Apply Current Crop to All images + + + all images in the folder will be cropped in reference to current + + + + + + + + + + + Save Current State + + + + + Normalize with another Stack + + + + + Stack Info + + + Diaplays details of the stack + + + + + Export Image + + + Saves displayed 2D image frame + + + + + + .. + + + Export Stack + + + Save current stack as is + + + false + + + + + Export Mean Image (XYZ to mean(XY)) + + + + + Plot All Possible Image Correlations + + + + + Export Image as CSV (XYZ to Z,X*Y) + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/multipleScatterFit.ui b/xmidas/uis/multipleScatterFit.ui index 7112364..64b9363 100644 --- a/xmidas/uis/multipleScatterFit.ui +++ b/xmidas/uis/multipleScatterFit.ui @@ -1,114 +1,114 @@ - - - MainWindow - - - - 0 - 0 - 856 - 628 - - - - MainWindow - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - 856 - 21 - - - - - View - - - - Plot Background - - - - - - - - - - - - - toolBar - - - TopToolBarArea - - - false - - - - - - - - Export - - - - - Save as PNG - - - - - Generate MultiColor Mask - - - - - Black - - - - - White - - - - - - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 856 + 628 + + + + MainWindow + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 856 + 21 + + + + + View + + + + Plot Background + + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + Export + + + + + Save as PNG + + + + + Generate MultiColor Mask + + + + + Black + + + + + White + + + + + + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/mutlichannel.ui b/xmidas/uis/mutlichannel.ui index 133f006..7315290 100644 --- a/xmidas/uis/mutlichannel.ui +++ b/xmidas/uis/mutlichannel.ui @@ -1,458 +1,464 @@ - - - MainWindow - - - - 0 - 0 - 674 - 765 - - - - MainWindow - - - font: 12pt "Segoe UI" - - - - QPushButton { -border: 1px solid #555; -border-radius: 5px; -background: qradialgradient(cx: 0.3, cy: -0.1, -fx: 0.7, fy: 0.1, -radius: 1, stop: 0 #fff, stop: 1 #888); -background-color: rgb(170, 255, 255); -} - -QPushButton:hover{ - background-color: rgb(255, 255, 0); - } - -QPushButton:pressed{ - background-color: rgb(0,255, 0); - } - -font: 12pt "Segoe UI"; - - - - 25 - - - 25 - - - 25 - - - 25 - - - - - - 0 - 0 - - - - - - - - Edit - - - - - - - 0 - 0 - - - - 0 - - - - - 0 - 0 - 316 - 85 - - - - Thresholding - - - - - - - - - 0 - 0 - - - - 0,100 - - - Qt::AlignCenter - - - - - - - - - - - - 0 - 0 - - - - Qt::NoFocus - - - click update after making changes - - - 100 - - - 5 - - - 5 - - - 100 - - - 100 - - - true - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 5 - - - - - - - - - - - - 0 - 0 - - - - Qt::NoFocus - - - click update after making changes - - - 100 - - - 5 - - - 5 - - - 0 - - - 0 - - - true - - - Qt::Horizontal - - - QSlider::NoTicks - - - 5 - - - - - - - - - - - - - - - 0 - 0 - 316 - 85 - - - - Opacity - - - - - - - - - 0 - 0 - - - - 1 - - - Qt::AlignCenter - - - - - - - click update after making changes - - - 100 - - - 10 - - - 100 - - - Qt::Horizontal - - - - - - - - - - - - - execute above changes to the selected item - - - Update - - - - - - - - - Show Selected - - - - - - - Show All - - - - - - - - - - 0 - 0 - - - - change properties of the selected item - - - font: 8pt "Segoe UI"; - - - QAbstractScrollArea::AdjustToContents - - - - - - - - - - 0 - 0 - - - - Change Selected To - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - - - - - - - - - - - - - - - 0 - 0 - 674 - 34 - - - - - File - - - true - - - - - - - - - - - - - Load Images - - - Select and load multiple tiff images to create a multi color view - - - - - Load 2 - - - - - Load 3 - - - - - Load 4 - - - - - Load 5 - - - - - Load 6 - - - - - Export Image - - - Export the image view as a sinle image file - - - - - Save State File - - - Save the current state of the view. Images and properties are saved - - - - - Load State File - - - Load a state (json file) saved previously. - - - - - Load Stack - - - Load images as a stack of tiff - - - - - - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 612 + 765 + + + + MainWindow + + + font: 12pt "Segoe UI" + + + + QPushButton { +border: 1px solid #555; +border-radius: 5px; +background: qradialgradient(cx: 0.3, cy: -0.1, +fx: 0.7, fy: 0.1, +radius: 1, stop: 0 #fff, stop: 1 #888); +background-color: rgb(170, 255, 255); +} + +QPushButton:hover{ + background-color: rgb(255, 255, 0); + } + +QPushButton:pressed{ + background-color: rgb(0,255, 0); + } + +font: 12pt "Segoe UI"; + + + + 25 + + + 25 + + + 25 + + + 25 + + + + + + 0 + 0 + + + + + + + + Edit + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + 0 + 254 + 85 + + + + Thresholding + + + + + + + + + 0 + 0 + + + + 0,100 + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + click update after making changes + + + 100 + + + 5 + + + 5 + + + 100 + + + 100 + + + true + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 5 + + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + click update after making changes + + + 100 + + + 5 + + + 5 + + + 0 + + + 0 + + + true + + + Qt::Horizontal + + + QSlider::NoTicks + + + 5 + + + + + + + + + + + + + + + 0 + 0 + 98 + 81 + + + + Opacity + + + + + + + + + 0 + 0 + + + + 1 + + + Qt::AlignCenter + + + + + + + click update after making changes + + + 100 + + + 10 + + + 100 + + + Qt::Horizontal + + + + + + + + + + + + + execute above changes to the selected item + + + Update + + + + + + + + + Show Selected + + + + + + + Show All + + + + + + + + + + 0 + 0 + + + + change properties of the selected item + + + font: 8pt "Segoe UI"; + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + 0 + 0 + + + + Change Selected To + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + + + + + + + + + + + + + + + 0 + 0 + 612 + 34 + + + + + File + + + true + + + + + + + + + + + + + + Load Images + + + Select and load multiple tiff images to create a multi color view + + + + + Load 2 + + + + + Load 3 + + + + + Load 4 + + + + + Load 5 + + + + + Load 6 + + + + + Export Image + + + Export the image view as a sinle image file + + + + + Save State File + + + Save the current state of the view. Images and properties are saved + + + + + Load State File + + + Load a state (json file) saved previously. + + + + + Load Stack + + + Load images as a stack of tiff + + + + + Save Stack (.tiff) + + + + + + GraphicsLayoutWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/singleStackView.ui b/xmidas/uis/singleStackView.ui index d832b04..22ee549 100644 --- a/xmidas/uis/singleStackView.ui +++ b/xmidas/uis/singleStackView.ui @@ -1,151 +1,151 @@ - - - MainWindow - - - - 0 - 0 - 691 - 566 - - - - MainWindow - - - QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, - stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 2px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} - -QPushButton { -background-color: rgb(175, 236, 255); -color: rgb(255, 0, 127); -} - - - - - - - - - - 0 - 0 - - - - - - - - - - Qt::Horizontal - - - - - - - font: 12pt "MS Shell Dlg 2"; - - - 10 - - - - - - - - - - - - - 0 - 0 - 691 - 26 - - - - - File - - - - - - - - - Save - - - - - - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 691 + 566 + + + + MainWindow + + + QSlider::groove:horizontal { +border: 1px solid #bbb; +background: white; +height: 10px; +border-radius: 4px; +} + +QSlider::sub-page:horizontal { +background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #66e, stop: 1 #bbf); +background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, + stop: 0 #bbf, stop: 1 #55f); +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::add-page:horizontal { +background: #fff; +border: 1px solid #777; +height: 10px; +border-radius: 4px; +} + +QSlider::handle:horizontal { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #eee, stop:1 #ccc); +border: 1px solid #777; +width: 13px; +margin-top: -2px; +margin-bottom: -2px; +border-radius: 4px; +} + +QSlider::handle:horizontal:hover { +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #fff, stop:1 #ddd); +border: 1px solid #444; +border-radius: 2px; +} + +QSlider::sub-page:horizontal:disabled { +background: #bbb; +border-color: #999; +} + +QSlider::add-page:horizontal:disabled { +background: #eee; +border-color: #999; +} + +QSlider::handle:horizontal:disabled { +background: #eee; +border: 1px solid #aaa; +border-radius: 4px; +} + +QPushButton { +background-color: rgb(175, 236, 255); +color: rgb(255, 0, 127); +} + + + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + font: 12pt "MS Shell Dlg 2"; + + + 10 + + + + + + + + + + + + + 0 + 0 + 691 + 26 + + + + + File + + + + + + + + + Save + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/xanesFitStat.ui b/xmidas/uis/xanesFitStat.ui index 25685bf..258e475 100644 --- a/xmidas/uis/xanesFitStat.ui +++ b/xmidas/uis/xanesFitStat.ui @@ -1,47 +1,47 @@ - - - MainWindow - - - - 0 - 0 - 792 - 481 - - - - MainWindow - - - - - - - - - - - - - - - 0 - 0 - 792 - 26 - - - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
+ + + MainWindow + + + + 0 + 0 + 792 + 481 + + + + MainWindow + + + + + + + + + + + + + + + 0 + 0 + 792 + 26 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/xmidas/uis/xrf_xanes_gui_3ID.ui b/xmidas/uis/xrf_xanes_gui_3ID.ui index 8755841..18a9043 100644 --- a/xmidas/uis/xrf_xanes_gui_3ID.ui +++ b/xmidas/uis/xrf_xanes_gui_3ID.ui @@ -1,756 +1,756 @@ - - - Ajith - MainWindow - - - - 0 - 0 - 820 - 553 - - - - HXN_Wizard - - - font: 12pt "MS Shell Dlg 2"; - - - - - - - - - - 0 - 0 - - - - - - - - 20 - - - - - - - - 0 - 0 - - - - - - - C:\Matrix\Blue Pill\Morpheus.csv - - - - - - - - 0 - 0 - - - - - - - C:\Matrix\Blue Pill - - - - - - - 0 - - - 10 - - - - - - 0 - 0 - - - - Select Working Directory - - - - - - - - 0 - 0 - - - - Select Paramater File - - - - - - - - 0 - 0 - - - - Select XANES Reference File (xanes only) - - - - - - - - - - 0 - 0 - - - - - - - C:\Matrix\Blue Pill\TheChosenOne.json - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - background-color: rgb(255, 221, 98); - - - Open PyXRF GUI - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - font: 12pt "MS Shell Dlg 2"; - - - 1 - - - false - - - - XRF - - - - - - XRF Batch Processing - - - Qt::AlignCenter - - - - - - 150 - - - 150 - - - - - 92158 - - - 00000 - - - - - - - 92102 - - - 00000 - - - - - - - Last Scan ID: - - - - - - - Scalar Name: - - - - - - - First Scan ID: - - - - - - - sclr1_ch4 - - - Fe_K - - - - - - - background-color: rgb(85, 255, 127); - - - Start Batch Processing - - - - - - - - - - - - 150 - - - 150 - - - - - XRF Live Processing - - - Qt::AlignCenter - - - - - - - - background-color: rgb(0, 170, 255); -background-color: rgb(67, 246, 255); - - - Initiate Live - - - - - - - background-color: rgb(255, 130, 67); -font: 75 12pt "MS Shell Dlg 2"; - - - - Start - - - - - - - Live Processing is not ready - - - Qt::AlignCenter - - - - - - - - - - - - - - - XANES - - - - - - 50 - - - 50 - - - - - - - First Scan ID: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 92102 - - - 00000 - - - - - - - XANES Element - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Fe_K - - - Fe_K - - - - - - - Alignemnt Element - - - - - - - P_K - - - Pt_L - - - - - - - - - 50 - - - 0 - - - - - Last Scan ID: - - - - - - - 92158 - - - 00000 - - - - - - - Scalar Name - - - - - - - sclr1_ch4 - - - Fe_K - - - - - - - Save All Elem tiff Stacks - - - true - - - - - - - - 0 - 0 - - - - Subtract Pre-edge - - - - - - - - - - - Qt::Horizontal - - - - 49 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 48 - - - - - - - - 20 - - - 20 - - - - - - 0 - 0 - - - - Fitting method - - - - - - - - 0 - 0 - - - - - nnls - - - - - admm - - - - - - - - false - - - - 0 - 0 - - - - Lambda for ADMM: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - false - - - - 0 - 0 - - - - Qt::StrongFocus - - - 5 - - - Lambda for ADMM - - - - - - - - 0 - 0 - - - - Energy Shift (eV) - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - 0 - - - Lambda for ADMM - - - - - - - Work Flow - - - - - - - - 0 - 0 - - - - - load_and_process - - - - - process - - - - - build_xanes_map - - - - - - - - font: 12pt "MS Shell Dlg 2"; -background-color: rgb(170, 255, 255); - - - Go - - - - - - - background-color: rgb(255, 77, 46); -color: rgb(0, 0, 0); -font: 75 10pt "MS Shell Dlg 2"; - - - Close All Plots - - - - - - - - - Qt::Horizontal - - - - 49 - 20 - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 52 - - - - - - - - - - 0 - 0 - 820 - 25 - - - - - HELP - - - - - - - - - + + + Ajith + MainWindow + + + + 0 + 0 + 820 + 553 + + + + HXN_Wizard + + + font: 12pt "MS Shell Dlg 2"; + + + + + + + + + + 0 + 0 + + + + + + + + 20 + + + + + + + + 0 + 0 + + + + + + + C:\Matrix\Blue Pill\Morpheus.csv + + + + + + + + 0 + 0 + + + + + + + C:\Matrix\Blue Pill + + + + + + + 0 + + + 10 + + + + + + 0 + 0 + + + + Select Working Directory + + + + + + + + 0 + 0 + + + + Select Paramater File + + + + + + + + 0 + 0 + + + + Select XANES Reference File (xanes only) + + + + + + + + + + 0 + 0 + + + + + + + C:\Matrix\Blue Pill\TheChosenOne.json + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + background-color: rgb(255, 221, 98); + + + Open PyXRF GUI + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + font: 12pt "MS Shell Dlg 2"; + + + 1 + + + false + + + + XRF + + + + + + XRF Batch Processing + + + Qt::AlignCenter + + + + + + 150 + + + 150 + + + + + 92158 + + + 00000 + + + + + + + 92102 + + + 00000 + + + + + + + Last Scan ID: + + + + + + + Scalar Name: + + + + + + + First Scan ID: + + + + + + + sclr1_ch4 + + + Fe_K + + + + + + + background-color: rgb(85, 255, 127); + + + Start Batch Processing + + + + + + + + + + + + 150 + + + 150 + + + + + XRF Live Processing + + + Qt::AlignCenter + + + + + + + + background-color: rgb(0, 170, 255); +background-color: rgb(67, 246, 255); + + + Initiate Live + + + + + + + background-color: rgb(255, 130, 67); +font: 75 12pt "MS Shell Dlg 2"; + + + + Start + + + + + + + Live Processing is not ready + + + Qt::AlignCenter + + + + + + + + + + + + + + + XANES + + + + + + 50 + + + 50 + + + + + + + First Scan ID: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 92102 + + + 00000 + + + + + + + XANES Element + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Fe_K + + + Fe_K + + + + + + + Alignemnt Element + + + + + + + P_K + + + Pt_L + + + + + + + + + 50 + + + 0 + + + + + Last Scan ID: + + + + + + + 92158 + + + 00000 + + + + + + + Scalar Name + + + + + + + sclr1_ch4 + + + Fe_K + + + + + + + Save All Elem tiff Stacks + + + true + + + + + + + + 0 + 0 + + + + Subtract Pre-edge + + + + + + + + + + + Qt::Horizontal + + + + 49 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 48 + + + + + + + + 20 + + + 20 + + + + + + 0 + 0 + + + + Fitting method + + + + + + + + 0 + 0 + + + + + nnls + + + + + admm + + + + + + + + false + + + + 0 + 0 + + + + Lambda for ADMM: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + + 0 + 0 + + + + Qt::StrongFocus + + + 5 + + + Lambda for ADMM + + + + + + + + 0 + 0 + + + + Energy Shift (eV) + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + 0 + + + Lambda for ADMM + + + + + + + Work Flow + + + + + + + + 0 + 0 + + + + + load_and_process + + + + + process + + + + + build_xanes_map + + + + + + + + font: 12pt "MS Shell Dlg 2"; +background-color: rgb(170, 255, 255); + + + Go + + + + + + + background-color: rgb(255, 77, 46); +color: rgb(0, 0, 0); +font: 75 10pt "MS Shell Dlg 2"; + + + Close All Plots + + + + + + + + + Qt::Horizontal + + + + 49 + 20 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 52 + + + + + + + + + + 0 + 0 + 820 + 25 + + + + + HELP + + + + + + + + + diff --git a/xmidas/utils.py b/xmidas/utils.py new file mode 100644 index 0000000..31067ac --- /dev/null +++ b/xmidas/utils.py @@ -0,0 +1,815 @@ +""" Helper Functions (make a class later)""" + + +import h5py +import logging +import numpy as np +import pandas as pd +import os +import scipy.optimize as opt +import scipy.stats as stats + +import sklearn.decomposition as sd +import sklearn.cluster as sc +from scipy.signal import savgol_filter +from skimage.transform import resize +from skimage import filters +from sklearn import linear_model +from larch.xafs import pre_edge, preedge, mback +from larch.io import read_ascii, read_athena +from larch import Group +import xraydb +from pystackreg import StackReg + +logger = logging.getLogger() + +def get_xrf_data(h='h5file'): + """ + get xrf stack from h5 data generated at NSLS-II beamlines + + Arguments: + h5/hdf5 file + + Returns: + norm_xrf_stack - xrf stack image normalized with Io + mono_e - excitation enegy used for xrf + beamline - identity of the beamline + Io_avg - an average Io value, used before taking log + + """ + + f = h5py.File(h, "r") + + if list(f.keys())[0] == "xrfmap": + logger.info("Data from HXN/TES/SRX") + beamline = f["xrfmap/scan_metadata"].attrs["scan_instrument_id"] + + try: + + beamline_scalar = {"HXN": 2, "SRX": 0, "TES": 0} + + if beamline in beamline_scalar.keys(): + + Io = np.array(f["xrfmap/scalers/val"])[:, :, beamline_scalar[beamline]] + raw_xrf_stack = np.array(f["xrfmap/detsum/counts"]) + norm_xrf_stack = raw_xrf_stack + Io_avg = int(remove_nan_inf(Io).mean()) + else: + logger.error("Unknown Beamline Scalar") + except Exception: + logger.warning("Unknown Scalar: Raw Detector count in use") + norm_xrf_stack = np.array(f["xrfmap/detsum/counts"]) + + elif list(f.keys())[0] == "xrmmap": + logger.info("Data from XFM") + beamline = "XFM" + raw_xrf_stack = np.array(f["xrmmap/mcasum/counts"]) + Io = np.array(f["xrmmap/scalars/I0"]) + norm_xrf_stack = raw_xrf_stack + Io_avg = int(remove_nan_inf(Io).mean()) + + elif list(f.keys())[0] == "MAPS": + logger.info("MAPS file") + beamline = "APS" + raw_xrf_stack = np.array(f["MAPS/Spectra/mca_arr"]) + Io = 1 #have to find the name of the scalar + norm_xrf_stack = raw_xrf_stack.transpose((1, 2, 0)) + Io_avg = int(remove_nan_inf(Io).mean()) + + else: + logger.error("Unknown Data Format") + + try: + mono_e = int(f["xrfmap/scan_metadata"].attrs["instrument_mono_incident_energy"] * 1000) + logger.info("Excitation energy was taken from the h5 data") + + except Exception: + mono_e = 12000 + logger.info(f"Unable to get Excitation energy from the h5 data; using default value {mono_e} KeV") + + return remove_nan_inf(norm_xrf_stack.transpose((2, 0, 1))), mono_e + 1500, beamline, Io_avg + + +def remove_nan_inf(im): + im = np.array(im, dtype=np.float32) + im[np.isnan(im)] = 0 + im[np.isinf(im)] = 0 + return im + + +def rebin_image(im, bin_factor): + arrx, arry = np.shape(im) + if arrx / bin_factor != int or arrx / bin_factor != int: + logger.error("Invalid Binning") + + else: + shape = (arrx / bin_factor, arry / bin_factor) + return im.reshape(shape).mean(-1).mean(1) + + +def remove_hot_pixels(image_array, NSigma=5): + image_array = remove_nan_inf(image_array) + a, b, c = np.shape(image_array) + img_stack2 = np.zeros((a, b, c)) + for i in range(a): + im = image_array[i, :, :] + im[abs(im) > np.std(im) * NSigma] = im.mean() + img_stack2[i, :, :] = im + return img_stack2 + + +def smoothen(image_array, w_size=5): + a, b, c = np.shape(image_array) + spec2D_Matrix = np.reshape(image_array, (a, (b * c))) + smooth2D_Matrix = savgol_filter(spec2D_Matrix, w_size, w_size - 2, axis=0) + return remove_nan_inf(np.reshape(smooth2D_Matrix, (a, b, c))) + + +def resize_stack(image_array, upscaling=False, scaling_factor=2): + en, im1, im2 = np.shape(image_array) + + if upscaling: + im1_ = im1 * scaling_factor + im2_ = im2 * scaling_factor + img_stack_resized = resize(image_array, (en, im1_, im2_)) + + else: + im1_ = int(im1 / scaling_factor) + im2_ = int(im2 / scaling_factor) + img_stack_resized = resize(image_array, (en, im1_, im2_)) + + return img_stack_resized + + +def normalize(image_array, norm_point=-1): + norm_stack = image_array / image_array[norm_point] + return remove_nan_inf(norm_stack) + + +def remove_edges(image_array): + # z, x, y = np.shape(image_array) + return image_array[:, 1:-1, 1:-1] + + +def background_value(image_array): + img = image_array.mean(0) + img_h = img.mean(0) + img_v = img.mean(1) + h = np.gradient(img_h) + v = np.gradient(img_v) + bg = np.min([img_h[h == h.max()], img_v[v == v.max()]]) + return bg + + +def background_subtraction(img_stack, bg_percentage=10): + img_stack = remove_nan_inf(img_stack) + a, b, c = np.shape(img_stack) + ref_image = np.reshape(img_stack.mean(0), (b * c)) + bg_ratio = int((b * c) * 0.01 * bg_percentage) + bg_ = np.max(sorted(ref_image)[0:bg_ratio]) + bged_img_stack = img_stack - bg_[:, np.newaxis, np.newaxis] + return bged_img_stack + + +def background_subtraction2(img_stack, bg_percentage=10): + img_stack = remove_nan_inf(img_stack) + a, b, c = np.shape(img_stack) + bg_ratio = int((b * c) * 0.01 * bg_percentage) + bged_img_stack = img_stack.copy() + + for n, img in enumerate(img_stack): + bg_ = np.max(sorted(img.flatten())[0:bg_ratio]) + print(bg_) + bged_img_stack[n] = img - bg_ + + return remove_nan_inf(bged_img_stack) + + +def background1(img_stack): + img = img_stack.sum(0) + img_h = img.mean(0) + img_v = img.mean(1) + h = np.gradient(img_h) + v = np.gradient(img_v) + bg = np.min([img_h[h == h.max()], img_v[v == v.max()]]) + return bg + + +def get_sum_spectra(image_array): + spec = np.sum(image_array, axis=(1, 2)) + return spec + + +def get_mean_spectra(image_array): + spec = np.mean(image_array, axis=(1, 2)) + return spec + + +def flatten_(image_array): + z, x, y = np.shape(image_array) + flat_array = np.reshape(image_array, (x * y, z)) + return flat_array + + +def image_to_pandas(image_array): + a, b, c = np.shape(image_array) + im_array = np.reshape(image_array, ((b * c), a)) + a, b = im_array.shape + df = pd.DataFrame( + data=im_array[:, :], columns=["e" + str(i) for i in range(b)], index=["s" + str(i) for i in range(a)] + ) + return df + + +def image_to_pandas2(image_array): + a, b, c = np.shape(image_array) + im_array = np.reshape(image_array, (a, (b * c))) + a, b = im_array.shape + df = pd.DataFrame( + data=im_array[:, :], index=["e" + str(i) for i in range(a)], columns=["s" + str(i) for i in range(b)] + ) + return df + + +def neg_log(image_array): + absorb = -1 * np.log(image_array) + return remove_nan_inf(absorb) + + +def clean_stack(img_stack, auto_bg=False, bg_percentage=5): + a, b, c = np.shape(img_stack) + + if auto_bg is True: + bg_ = background1(img_stack) + + else: + sum_spec = (img_stack.sum(1)).sum(1) + ref_stk_num = np.where(sum_spec == sum_spec.max())[-1] + + ref_image = np.reshape(img_stack[ref_stk_num], (b * c)) + bg_ratio = int((b * c) * 0.01 * bg_percentage) + bg_ = np.max(sorted(ref_image)[0:bg_ratio]) + + bg = np.where(img_stack[ref_stk_num] > bg_, img_stack[ref_stk_num], 0) + bg2 = np.where(bg < bg_, bg, 1) + + bged_img_stack = img_stack * bg2 + + return remove_nan_inf(bged_img_stack) + + +def subtractBackground(im_stack, bg_region): + if bg_region.ndim == 3: + bg_region_ = np.mean(bg_region, axis=(1, 2)) + + elif bg_region.ndim == 2: + bg_region_ = np.mean(bg_region, axis=1) + + else: + bg_region_ = bg_region + + return im_stack - bg_region_[:, np.newaxis, np.newaxis] + + +def classify(img_stack, correlation="Pearson"): + img_stack_ = img_stack + a, b, c = np.shape(img_stack_) + norm_img_stack = normalize(img_stack_) + f = np.reshape(norm_img_stack, (a, (b * c))) + + max_x, max_y = np.where(norm_img_stack.sum(0) == (norm_img_stack.sum(0)).max()) + ref = norm_img_stack[:, int(max_x), int(max_y)] + corr = np.zeros(len(f.T)) + for s in range(len(f.T)): + if correlation == "Kendall": + r, p = stats.kendalltau(ref, f.T[s]) + elif correlation == "Pearson": + r, p = stats.pearsonr(ref, f.T[s]) + + corr[s] = r + + cluster_image = np.reshape(corr, (b, c)) + return (cluster_image ** 3), img_stack_ + + +def correlation_kmeans(img_stack, n_clusters, correlation="Pearson"): + img, bg_image = classify(img_stack, correlation) + img[np.isnan(img)] = -99999 + X = img.reshape((-1, 1)) + k_means = sc.KMeans(n_clusters) + k_means.fit(X) + + X_cluster = k_means.labels_ + X_cluster = X_cluster.reshape(img.shape) + 1 + + return X_cluster + + +def cluster_stack( + im_array, method="KMeans", n_clusters_=4, decomposed=False, decompose_method="PCA", decompose_comp=2 +): + a, b, c = im_array.shape + + if method == "Correlation-Kmeans": + + X_cluster = correlation_kmeans(im_array, n_clusters_, correlation="Pearson") + + else: + + methods = { + "MiniBatchKMeans": sc.MiniBatchKMeans, + "KMeans": sc.KMeans, + "MeanShift": sc.MeanShift, + "Spectral Clustering": sc.SpectralClustering, + "Affinity Propagation": sc.AffinityPropagation, + } + + if decomposed: + im_array = denoise_with_decomposition(im_array, method_=decompose_method, n_components=decompose_comp) + + flat_array = np.reshape(im_array, (a, (b * c))) + init_cluster = methods[method](n_clusters=n_clusters_) + init_cluster.fit(np.transpose(flat_array)) + X_cluster = init_cluster.labels_.reshape(b, c) + 1 + + decon_spectra = np.zeros((a, n_clusters_)) + decon_images = np.zeros((n_clusters_, b, c)) + + for i in range(n_clusters_): + mask_i = np.where(X_cluster == (i + 1), X_cluster, 0) + spec_i = get_sum_spectra(im_array * mask_i) + decon_spectra[:, i] = spec_i + decon_images[i] = im_array.sum(0) * mask_i + + return decon_images, X_cluster, decon_spectra + +def kmeans_variance(im_array): + a, b, c = im_array.shape + flat_array = np.reshape(im_array, (a, (b * c))) + var = np.arange(24) + clust_n = np.arange(24) + 2 + + for clust in var: + init_cluster = sc.KMeans(n_clusters=int(clust + 2)) + init_cluster.fit(np.transpose(flat_array)) + var_ = init_cluster.inertia_ + var[clust] = np.float64(var_) + + return clust_n, var + + +def pca_scree(im_stack): + new_image = im_stack.transpose(2, 1, 0) + x, y, z = np.shape(new_image) + img_ = np.reshape(new_image, (x * y, z)) + # pca = sd.PCA(z) + # pca.fit(img_) + pca = sd.TruncatedSVD(z - 1) + pca.fit(img_) + var = pca.singular_values_ + # var = pca.singular_values_ + return var + + +def decompose_stack(im_stack, decompose_method="PCA", n_components_=3): + new_image = im_stack.transpose(2, 1, 0) + new_image[new_image<0] = 0 + + x, y, z = np.shape(new_image) + img_ = np.reshape(new_image, (x * y, z)) + methods_dict = { + "PCA": sd.PCA, + "IncrementalPCA": sd.IncrementalPCA, + "NMF": sd.NMF, + "FastICA": sd.FastICA, + "DictionaryLearning": sd.MiniBatchDictionaryLearning, + "FactorAnalysis": sd.FactorAnalysis, + "TruncatedSVD": sd.TruncatedSVD, + } + + _mdl = methods_dict[decompose_method](n_components=n_components_) + + ims = (_mdl.fit_transform(img_).reshape(x, y, n_components_)).transpose(2, 1, 0) + spcs = _mdl.components_.transpose() + decon_spetra = np.zeros((z, n_components_)) + decom_map = np.zeros((ims.shape)) + + for i in range(n_components_): + f = ims.copy()[i] + f[f < 0] = 0 + f = np.where(f > 3 * np.std(f), f, 0) + spec_i = ((new_image.T * f).sum(1)).sum(1) + decon_spetra[:, i] = spec_i + + f[f > 0] = i + 1 + decom_map[i] = f + decom_map = decom_map.sum(0) + + return np.float32(ims), spcs, decon_spetra, decom_map + + +def denoise_with_decomposition(img_stack, method_="PCA", n_components=4): + new_image = img_stack.transpose(2, 1, 0) + x, y, z = np.shape(new_image) + img_ = np.reshape(new_image, (x * y, z)) + + methods_dict = { + "PCA": sd.PCA, + "IncrementalPCA": sd.IncrementalPCA, + "NMF": sd.NMF, + "FastICA": sd.FastICA, + "DictionaryLearning": sd.DictionaryLearning, + "FactorAnalysis": sd.FactorAnalysis, + "TruncatedSVD": sd.TruncatedSVD, + } + + decomposed = methods_dict[method_](n_components=n_components) + + ims = (decomposed.fit_transform(img_).reshape(x, y, n_components)).transpose(2, 1, 0) + ims[ims < 0] = 0 + ims[ims > 0] = 1 + mask = ims.sum(0) + mask[mask > 1] = 1 + # mask = uniform_filter(mask) + filtered = img_stack * mask + # plt.figure() + # plt.imshow(filtered.sum(0)) + # plt.title('background removed') + # plt.show() + return remove_nan_inf(filtered) + + +def interploate_E(refs, e): + n = np.shape(refs)[1] + refs = np.array(refs) + ref_e = refs[:, 0] + ref = refs[:, 1:n] + all_ref = [] + for i in range(n - 1): + ref_i = np.interp(e, ref_e, ref[:, i]) + all_ref.append(ref_i) + return np.array(all_ref) + + +def getStats(spec, fit, num_refs=2): + stats = {} + + r_factor = (np.sum(spec - fit) ** 2) / np.sum(spec ** 2) + stats["R_Factor"] = np.around(r_factor, 5) + + y_mean = np.sum(spec) / len(spec) + SS_tot = np.sum((spec - y_mean) ** 2) + SS_res = np.sum((spec - fit) ** 2) + r_square = 1 - (SS_res / SS_tot) + stats["R_Square"] = np.around(r_square, 4) + + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.chisquare.html + # https://en.wikipedia.org/wiki/Chi-squared_distribution + # https://ned.ipac.caltech.edu/level5/Leo/Stats2_4.html + chisq = np.sum(((spec - fit) ** 2) / np.var(spec)) + # chisq = np.sum((spec - fit) ** 2) + stats["Chi_Square"] = np.around(chisq, 5) + + red_chisq = chisq / (len(spec) - num_refs) + stats["Reduced Chi_Square"] = np.around(red_chisq, 5) + + return stats + + +def xanes_fitting_1D(spec, e_list, refs, method="NNLS", alphaForLM=0.01): + """Linear combination fit of image data with reference standards""" + + int_refs = interploate_E(refs, e_list) + + if method == "NNLS": + coeffs, r = opt.nnls(int_refs.T, spec) + + elif method == "LASSO": + lasso = linear_model.Lasso(positive=True, alpha=alphaForLM) # lowering alpha helps with 1D fits + fit_results = lasso.fit(int_refs.T, spec) + coeffs = fit_results.coef_ + + elif method == "RIDGE": + ridge = linear_model.Ridge(alpha=alphaForLM) + fit_results = ridge.fit(int_refs.T, spec) + coeffs = fit_results.coef_ + + fit = coeffs @ int_refs + stats = getStats(spec, fit, num_refs=np.min(np.shape(int_refs.T))) + + return stats, np.around(coeffs, 4) + + +def xanes_fitting(im_stack, e_list, refs, method="NNLS", alphaForLM=0.1, binStack=False): + """Linear combination fit of image data with reference standards""" + + if binStack: + im_stack = resize_stack(im_stack, scaling_factor=4) + + en, im1, im2 = np.shape(im_stack) + im_array = im_stack.reshape(en, im1 * im2) + coeffs_arr = [] + r_factor_arr = [] + lasso = linear_model.Lasso(positive=True, alpha=alphaForLM) + for n, i in enumerate(range(im1 * im2)): + stats, coeffs = xanes_fitting_1D(im_array[:, i], e_list, refs, method=method, alphaForLM=alphaForLM) + coeffs_arr.append(coeffs) + r_factor_arr.append(stats["R_Factor"]) + + abundance_map = np.reshape(coeffs_arr, (im1, im2, -1)) + r_factor_im = np.reshape(r_factor_arr, (im1, im2)) + + return abundance_map, r_factor_im, np.mean(coeffs_arr, axis=0) + + +def xanes_fitting_Line(im_stack, e_list, refs, method="NNLS", alphaForLM=0.05): + """Linear combination fit of image data with reference standards""" + en, im1, im2 = np.shape(im_stack) + im_array = np.mean(im_stack, 2) + coeffs_arr = [] + meanStats = {"R_Factor": 0, "R_Square": 0, "Chi_Square": 0, "Reduced Chi_Square": 0} + + for i in range(im1): + stats, coeffs = xanes_fitting_1D(im_array[:, i], e_list, refs, method=method, alphaForLM=alphaForLM) + coeffs_arr.append(coeffs) + for key in stats.keys(): + meanStats[key] += stats[key] + + for key, vals in meanStats.items(): + meanStats[key] = np.around((vals / im1), 5) + + return meanStats, np.mean(coeffs_arr, axis=0) + + +def xanes_fitting_Binned(im_stack, e_list, refs, method="NNLS", alphaForLM=0.05): + """Linear combination fit of image data with reference standards""" + + im_stack = resize_stack(im_stack, scaling_factor=10) + # use a simple filter to find threshold value + val = filters.threshold_otsu(im_stack[-1]) + en, im1, im2 = np.shape(im_stack) + im_array = im_stack.reshape(en, im1 * im2) + coeffs_arr = [] + meanStats = {"R_Factor": 0, "R_Square": 0, "Chi_Square": 0, "Reduced Chi_Square": 0} + + specs_fitted = 0 + total_spec = im1 * im2 + for i in range(total_spec): + spec = im_array[:, i] + # do not fit low intensity/background regions + if spec[-1] > val: + specs_fitted += 1 + stats, coeffs = xanes_fitting_1D(spec / spec[-1], e_list, refs, method=method, alphaForLM=alphaForLM) + coeffs_arr.append(coeffs) + for key in stats.keys(): + meanStats[key] += stats[key] + else: + pass + + for key, vals in meanStats.items(): + meanStats[key] = np.around((vals / specs_fitted), 6) + # print(f"{specs_fitted}/{total_spec}") + return meanStats, np.mean(coeffs_arr, axis=0) + + +def create_df_from_nor(athenafile="fe_refs.nor"): + """create pandas dataframe from athena nor file, first column + is energy and headers are sample names""" + + refs = np.loadtxt(athenafile) + n_refs = refs.shape[-1] + skip_raw_n = n_refs + 6 + + df = pd.read_table( + athenafile, delim_whitespace=True, skiprows=skip_raw_n, header=None, usecols=np.arange(0, n_refs) + ) + df2 = pd.read_table( + athenafile, delim_whitespace=True, skiprows=skip_raw_n - 1, usecols=np.arange(0, n_refs + 1) + ) + new_col = df2.columns.drop("#") + df.columns = new_col + return df, list(new_col) + + +def create_df_from_nor_try2(athenafile="fe_refs.nor"): + """create pandas dataframe from athena nor file, first column + is energy and headers are sample names""" + + refs = np.loadtxt(athenafile) + n_refs = refs.shape[-1] + df_refs = pd.DataFrame(refs) + + df = pd.read_csv(athenafile, header=None) + new_col = list((str(df.iloc[n_refs + 5].values)).split(" ")[2::2]) + df_refs.columns = new_col + + return df_refs, list(new_col) + + +def energy_from_logfile(logfile="maps_log_tiff.txt"): + df = pd.read_csv(logfile, header=None, delim_whitespace=True, skiprows=9) + return df[9][df[7] == "energy"].values.astype(float) + + +def xanesNormalization(e, mu, e0=7125, step=None, + nnorm=2, nvict=0, pre1=None, pre2=-50, + norm1=100, norm2=None, method="pre_edge", + useFlattened=False, Elemline = "Fe_K"): + + elem, line = Elemline.split('_') + elemZ = xraydb.atomic_number(elem) + dat = Group(name='larchgroup', col1=e, col2=mu) + + + if method == "guess": + result = preedge(e, mu, e0, step=step, nnorm=nnorm, nvict=nvict) + + return result["pre1"], result["pre2"], result["norm1"], result["norm2"] + + elif method == "mback": + mback(e,mu, group=dat, z=elemZ, edge=line, e0=e0,fit_erfc=False) + return dat.f2, dat.fpp + + else: + pre_edge(e, mu,group=dat,e0=e0, step=step, nnorm=nnorm,nvict=nvict, pre1=pre1, + pre2=pre2, norm1=norm1,norm2=norm2, make_flat = True) + + if useFlattened: + normSpec = dat.flat + else: + normSpec = dat.norm + + return dat.pre_edge, dat.post_edge, normSpec + + +def xanesNormStack(e_list, im_stack, e0=7125, step=None, + nnorm=2, nvict=0, pre1=None, pre2=-50, + norm1=100, norm2=None, useFlattened=False, ignorePostEdgeNorm=False): + en, im1, im2 = np.shape(im_stack) + im_array = im_stack.reshape(en, im1 * im2) + normedStackArray = np.zeros_like(im_array) + dat = Group(name='larchgroup', col1=e_list, col2=get_mean_spectra(im_stack)) + + + for i in range(im1 * im2): + pre_edge(e_list, im_array[:, i], e0=e0, group=dat, step=step, nnorm=nnorm, + nvict=nvict, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2,make_flat = True) + + if useFlattened: + normSpec = dat.flat + else: + normSpec = dat.norm + + if ignorePostEdgeNorm: + normedStackArray[:, i] = normSpec * dat.post_edge + else: + normedStackArray[:, i] = normSpec + + return remove_nan_inf(np.reshape(normedStackArray, (en, im1, im2))) + +def getDeconvolutedXANESSpectrum(xanesStack, chemMapStack, energy, clusterSigma=1): + compXanesSpetraAll = pd.DataFrame() + compXanesSpetraAll['Energy'] = energy + + for n, compImage in enumerate(chemMapStack): + mask = np.where(compImage > clusterSigma * np.std(compImage), compImage, 0) + compXanesSpetraAll[f'Component_{n + 1}'] = get_mean_spectra(xanesStack * mask) + return compXanesSpetraAll + + +def align_stack( + stack_img, ref_image_void=True, ref_stack=None, transformation=StackReg.TRANSLATION, reference="previous" +): + + """Image registration flow using pystack reg""" + + # all the options are in one function + + sr = StackReg(transformation) + + if ref_image_void: + tmats_ = sr.register_stack(stack_img, reference=reference) + + else: + tmats_ = sr.register_stack(ref_stack, reference=reference) + out_ref = sr.transform_stack(ref_stack) + + out_stk = sr.transform_stack(stack_img, tmats=tmats_) + return np.float32(out_stk), tmats_ + + +def align_simple(stack_img, transformation=StackReg.TRANSLATION, reference="previous"): + + sr = StackReg(transformation) + tmats_ = sr.register_stack(stack_img, reference="previous") + for i in range(10): + out_stk = sr.transform_stack(stack_img, tmats=tmats_) + return np.float32(out_stk) + + +def align_with_tmat(stack_img, tmat_file, transformation=StackReg.TRANSLATION): + + sr = StackReg(transformation) + out_stk = sr.transform_stack(stack_img, tmats=tmat_file) + return np.float32(out_stk) + + +def align_stack_iter( + stack, + ref_stack_void=True, + ref_stack=None, + transformation=StackReg.TRANSLATION, + method=("previous", "first"), + max_iter=2, +): + if ref_stack_void: + ref_stack = stack + + for i in range(max_iter): + sr = StackReg(transformation) + for ii in range(len(method)): + print(ii, method[ii]) + tmats = sr.register_stack(ref_stack, reference=method[ii]) + ref_stack = sr.transform_stack(ref_stack) + stack = sr.transform_stack(stack, tmats=tmats) + + return np.float32(stack) + + +def applyMaskGetMeanSpectrum(im_stack, mask): + """A 2d mask to multiply with the 3d xanes stack and returns mean spectrum""" + + masked_stack = im_stack * mask + return get_mean_spectra(masked_stack) + + +def modifyStack( + raw_stack, + normalizeStack=False, + normToPoint=-1, + applySmooth=False, + smoothWindowSize=3, + applyThreshold=False, + thresholdValue=0, + removeOutliers=False, + nSigmaOutlier=3, + applyTranspose=False, + transposeVals=(0, 1, 2), + applyCrop=False, + cropVals=(0, 1, 2), + removeEdges=False, + resizeStack=False, + upScaling=False, + binFactor=2, +): + + """A giant function to modify the stack with many possible operations. + all the changes can be saved to a jason file as a config file. Enabling and + distabling the sliders is a problem""" + + """ + normStack = normalize(raw_stack, norm_point=normToPoint) + smoothStack = smoothen(raw_stack, w_size= smoothWindowSize) + thresholdStack = clean_stack(raw_stack, auto_bg=False, bg_percentage = thresholdValue) + outlierStack = remove_hot_pixels(raw_stack, NSigma=nSigmaOutlier) + transposeStack = np.transpose(raw_stack, transposeVals) + croppedStack = raw_stack[cropVals] + edgeStack = remove_edges(raw_stack) + binnedStack = resize_stack(raw_stack,upscaling=upScaling,scaling_factor=binFactor) + + """ + + if removeOutliers: + modStack = remove_hot_pixels(raw_stack, NSigma=nSigmaOutlier) + + else: + modStack = raw_stack + + if applyThreshold: + modStack = clean_stack(modStack, auto_bg=False, bg_percentage=thresholdValue) + + else: + pass + + if applySmooth: + modStack = smoothen(modStack, w_size=smoothWindowSize) + + else: + pass + + if applyTranspose: + modStack = np.transpose(modStack, transposeVals) + + else: + pass + + if applyCrop: + modStack = modStack[cropVals] + + else: + pass + + if normalizeStack: + modStack = normalize(raw_stack, norm_point=normToPoint) + else: + pass