diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..9aa279f
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,18 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+pyqt6 = "*"
+numpy = "*"
+matplotlib = "*"
+scikit-learn = "*"
+boto3 = "*"
+opencv-python = "*"
+black = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.13"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..dee8ee9
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,716 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "8030168f777edcbdfd2b954998e61cd68f938e2c6484ec1ed624794d81b17499"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.13"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "black": {
+ "hashes": [
+ "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f",
+ "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd",
+ "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea",
+ "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981",
+ "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b",
+ "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7",
+ "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8",
+ "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175",
+ "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d",
+ "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392",
+ "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad",
+ "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f",
+ "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f",
+ "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b",
+ "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875",
+ "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3",
+ "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800",
+ "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65",
+ "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2",
+ "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812",
+ "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50",
+ "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.9'",
+ "version": "==24.10.0"
+ },
+ "boto3": {
+ "hashes": [
+ "sha256:6d473f0f340d02b4e9ad5b8e68786a09728101a8b950231b89ebdaf72b6dca21",
+ "sha256:b36feae061dc0793cf311468956a0a9e99215ce38bc99a1a4e55a5b105f16297"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==1.36.6"
+ },
+ "botocore": {
+ "hashes": [
+ "sha256:4864c53d638da191a34daf3ede3ff1371a3719d952cc0c6bd24ce2836a38dd77",
+ "sha256:f77bbbb03fb420e260174650fb5c0cc142ec20a96967734eed2b0ef24334ef34"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==1.36.6"
+ },
+ "click": {
+ "hashes": [
+ "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2",
+ "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==8.1.8"
+ },
+ "contourpy": {
+ "hashes": [
+ "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1",
+ "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda",
+ "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d",
+ "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509",
+ "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6",
+ "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f",
+ "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e",
+ "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751",
+ "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86",
+ "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b",
+ "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc",
+ "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546",
+ "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec",
+ "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f",
+ "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82",
+ "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c",
+ "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b",
+ "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c",
+ "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c",
+ "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53",
+ "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80",
+ "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242",
+ "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85",
+ "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124",
+ "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5",
+ "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2",
+ "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3",
+ "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d",
+ "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc",
+ "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342",
+ "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1",
+ "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1",
+ "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595",
+ "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30",
+ "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab",
+ "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3",
+ "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2",
+ "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd",
+ "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7",
+ "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277",
+ "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453",
+ "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697",
+ "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b",
+ "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454",
+ "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9",
+ "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1",
+ "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6",
+ "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291",
+ "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750",
+ "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699",
+ "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e",
+ "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81",
+ "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9",
+ "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"
+ ],
+ "markers": "python_version >= '3.10'",
+ "version": "==1.3.1"
+ },
+ "cycler": {
+ "hashes": [
+ "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
+ "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==0.12.1"
+ },
+ "fonttools": {
+ "hashes": [
+ "sha256:03701e7de70c71eb5965cb200986b0c11dfa3cf8e843e4f517ee30a0f43f0a25",
+ "sha256:07478132407736ee5e54f9f534e73923ae28e9bb6dba17764a35e3caf7d7fea3",
+ "sha256:0879f99eabbf2171dfadd9c8c75cec2b7b3aa9cd1f3955dd799c69d60a5189ef",
+ "sha256:09ed667c4753e1270994e5398cce8703e6423c41702a55b08f843b2907b1be65",
+ "sha256:0ee6ed68af8d57764d69da099db163aaf37d62ba246cfd42f27590e3e6724b55",
+ "sha256:1062daa0390b32bfd062ded2b450db9e9cf10e5a9919561c13f535e818b1952b",
+ "sha256:127999618afe3a2490fad54bab0650c5fbeab1f8109bdc0205f6ad34306deb8b",
+ "sha256:132fa22be8a99784de8cb171b30425a581f04a40ec1c05183777fb2b1fe3bac9",
+ "sha256:1beb4647a0df5ceaea48015656525eb8081af226fe96554089fd3b274d239ef0",
+ "sha256:2d15e02b93a46982a8513a208e8f89148bca8297640527365625be56151687d0",
+ "sha256:2d419483a6295e83cabddb56f1c7b7bfdc8169de2fcb5c68d622bd11140355f9",
+ "sha256:34405f1314f1e88b1877a9f9e497fe45190e8c4b29a6c7cd85ed7f666a57d702",
+ "sha256:3aa6c684007723895aade9b2fe76d07008c9dc90fd1ef6c310b3ca9c8566729f",
+ "sha256:3f4e88f15f5ed4d2e4bdfcc98540bb3987ae25904f9be304be9a604e7a7050a1",
+ "sha256:4259159715142c10b0f4d121ef14da3fa6eafc719289d9efa4b20c15e57fef82",
+ "sha256:471961af7a4b8461fac0c8ee044b4986e6fe3746d4c83a1aacbdd85b4eb53f93",
+ "sha256:51120695ee13001533e50abd40eec32c01b9c6f44c5567db38a7acd3eedcd19d",
+ "sha256:57d55fc965e5dd20c8a60d880e0f43bafb506be87af0b650bdc42591e41e0d0d",
+ "sha256:5b7535a5ac386e549e2b00b34c59b53f805e2423000676723b6867df3c10df04",
+ "sha256:61aa1997c520bee4cde14ffabe81efc4708c500c8c81dce37831551627a2be56",
+ "sha256:73a4aaf672e7b2265c6354a69cbbadf71b7f3133ecb74e98fec4c67c366698a3",
+ "sha256:73bdff9c44d36c57ea84766afc20517eda0c9bb1571b4a09876646264bd5ff3b",
+ "sha256:76ac5a595f86892b49ba86ba2e46185adc76328ce6eff0583b30e5c3ab02a914",
+ "sha256:791e0cf862cdd3a252df395f1bb5f65e3a760f1da3c7ce184d0f7998c266614d",
+ "sha256:7954ea66a8d835f279c17d8474597a001ddd65a2c1ca97e223041bfbbe11f65e",
+ "sha256:8398928acb8a57073606feb9a310682d4a7e2d7536f2c61719261f4c0974504c",
+ "sha256:860ab9ed3f9e088d3bdb77b9074e656635f173b039e77d550b603cba052a0dca",
+ "sha256:88f74bc19dbab3dee6a00ca67ca54bb4793e44ff0c4dcf1fa61d68651ae3fa0a",
+ "sha256:8a8004a19195eb8a8a13de69e26ec9ed60a5bc1fde336d0021b47995b368fac9",
+ "sha256:8c9de8d16d02ecc8b65e3f3d2d1e3002be2c4a3f094d580faf76d7f768bd45fe",
+ "sha256:9394813cc73fa22c5413ec1c5745c0a16f68dd2b890f7c55eaba5cb40187ed55",
+ "sha256:94f7f2c5c5f3a6422e954ecb6d37cc363e27d6f94050a7ed3f79f12157af6bb2",
+ "sha256:9f99e7876518b2d059a9cc67c506168aebf9c71ac8d81006d75e684222f291d2",
+ "sha256:9fb545f3a4ebada908fa717ec732277de18dd10161f03ee3b3144d34477804de",
+ "sha256:a55489c7e9d5ea69690a2afad06723c3d0c48c6d276a25391ea97cb31a16b37c",
+ "sha256:a632f85bd73e002b771bcbcdc512038fa5d2e09bb18c03a22fb8d400ea492ddf",
+ "sha256:ac817559a7d245454231374e194b4e457dca6fefa5b52af466ab0516e9a09c6e",
+ "sha256:acc74884afddc2656bffc50100945ff407574538c152931c402fccddc46f0abc",
+ "sha256:af5469bbf555047efd8752d85faeb2a3510916ddc6c50dd6fb168edf1677408f",
+ "sha256:bc6f58976ffc19fe1630119a2736153b66151d023c6f30065f31c9e8baed1303",
+ "sha256:c2f78ebfdef578d4db7c44bc207ac5f9a5c1f22c9db606460dcc8ad48e183338",
+ "sha256:c42009177d3690894288082d5e3dac6bdc9f5d38e25054535e341a19cf5183a4",
+ "sha256:d20ab5a78d0536c26628eaadba661e7ae2427b1e5c748a0a510a44d914e1b155",
+ "sha256:d3226d40cb92787e09dcc3730f54b3779dfe56bdfea624e263685ba17a6faac4",
+ "sha256:d77d83ca77a4c3156a2f4cbc7f09f5a8503795da658fa255b987ad433a191266",
+ "sha256:d91fce2e9a87cc0db9f8042281b6458f99854df810cfefab2baf6ab2acc0f4b4",
+ "sha256:e1c06fbc2fd76b9bab03eddfd8aa9fb7c0981d314d780e763c80aa76be1c9982",
+ "sha256:e82772f70b84e17aa36e9f236feb2a4f73cb686ec1e162557a36cf759d1acd58",
+ "sha256:edf159a8f1e48dc4683a715b36da76dd2f82954b16bfe11a215d58e963d31cfc",
+ "sha256:f66561fbfb75785d06513b8025a50be37bf970c3c413e87581cc6eff10bc78f1"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==4.55.6"
+ },
+ "jmespath": {
+ "hashes": [
+ "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
+ "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==1.0.1"
+ },
+ "joblib": {
+ "hashes": [
+ "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6",
+ "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==1.4.2"
+ },
+ "kiwisolver": {
+ "hashes": [
+ "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50",
+ "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c",
+ "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8",
+ "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc",
+ "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f",
+ "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79",
+ "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6",
+ "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2",
+ "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605",
+ "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09",
+ "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab",
+ "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e",
+ "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc",
+ "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8",
+ "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7",
+ "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880",
+ "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b",
+ "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b",
+ "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff",
+ "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3",
+ "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c",
+ "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0",
+ "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6",
+ "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30",
+ "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47",
+ "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0",
+ "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1",
+ "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90",
+ "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d",
+ "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b",
+ "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c",
+ "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a",
+ "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e",
+ "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc",
+ "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16",
+ "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a",
+ "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712",
+ "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c",
+ "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3",
+ "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc",
+ "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561",
+ "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d",
+ "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc",
+ "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db",
+ "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed",
+ "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751",
+ "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957",
+ "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165",
+ "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2",
+ "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476",
+ "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84",
+ "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246",
+ "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4",
+ "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25",
+ "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d",
+ "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271",
+ "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb",
+ "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31",
+ "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e",
+ "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85",
+ "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b",
+ "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7",
+ "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03",
+ "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b",
+ "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d",
+ "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a",
+ "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d",
+ "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3",
+ "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67",
+ "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f",
+ "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c",
+ "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502",
+ "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062",
+ "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954",
+ "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb",
+ "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a",
+ "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b",
+ "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed",
+ "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34",
+ "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"
+ ],
+ "markers": "python_version >= '3.10'",
+ "version": "==1.4.8"
+ },
+ "matplotlib": {
+ "hashes": [
+ "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6",
+ "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908",
+ "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6",
+ "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2",
+ "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae",
+ "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea",
+ "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede",
+ "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59",
+ "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765",
+ "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12",
+ "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683",
+ "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593",
+ "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1",
+ "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c",
+ "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5",
+ "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a",
+ "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03",
+ "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef",
+ "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff",
+ "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25",
+ "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3",
+ "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06",
+ "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8",
+ "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e",
+ "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95",
+ "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf",
+ "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef",
+ "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278",
+ "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc",
+ "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442",
+ "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997",
+ "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a",
+ "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e",
+ "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.10'",
+ "version": "==3.10.0"
+ },
+ "mypy-extensions": {
+ "hashes": [
+ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
+ "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.0"
+ },
+ "numpy": {
+ "hashes": [
+ "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f",
+ "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0",
+ "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd",
+ "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2",
+ "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4",
+ "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648",
+ "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be",
+ "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb",
+ "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160",
+ "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd",
+ "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a",
+ "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84",
+ "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e",
+ "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748",
+ "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825",
+ "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60",
+ "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957",
+ "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715",
+ "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317",
+ "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e",
+ "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283",
+ "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278",
+ "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9",
+ "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de",
+ "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369",
+ "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb",
+ "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189",
+ "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014",
+ "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323",
+ "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e",
+ "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49",
+ "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50",
+ "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d",
+ "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37",
+ "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39",
+ "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576",
+ "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a",
+ "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba",
+ "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7",
+ "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826",
+ "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467",
+ "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495",
+ "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc",
+ "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391",
+ "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0",
+ "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97",
+ "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c",
+ "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac",
+ "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369",
+ "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8",
+ "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2",
+ "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff",
+ "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a",
+ "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df",
+ "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.10'",
+ "version": "==2.2.2"
+ },
+ "opencv-python": {
+ "hashes": [
+ "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4",
+ "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec",
+ "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202",
+ "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a",
+ "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d",
+ "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b",
+ "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.6'",
+ "version": "==4.11.0.86"
+ },
+ "packaging": {
+ "hashes": [
+ "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
+ "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==24.2"
+ },
+ "pathspec": {
+ "hashes": [
+ "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08",
+ "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==0.12.1"
+ },
+ "pillow": {
+ "hashes": [
+ "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83",
+ "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96",
+ "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65",
+ "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a",
+ "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352",
+ "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f",
+ "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20",
+ "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c",
+ "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114",
+ "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49",
+ "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91",
+ "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0",
+ "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2",
+ "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5",
+ "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884",
+ "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e",
+ "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c",
+ "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196",
+ "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756",
+ "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861",
+ "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269",
+ "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1",
+ "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb",
+ "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a",
+ "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081",
+ "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1",
+ "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8",
+ "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90",
+ "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc",
+ "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5",
+ "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1",
+ "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3",
+ "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35",
+ "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f",
+ "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c",
+ "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2",
+ "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2",
+ "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf",
+ "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65",
+ "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b",
+ "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442",
+ "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2",
+ "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade",
+ "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482",
+ "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe",
+ "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc",
+ "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a",
+ "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec",
+ "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3",
+ "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a",
+ "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07",
+ "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6",
+ "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f",
+ "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e",
+ "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192",
+ "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0",
+ "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6",
+ "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73",
+ "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f",
+ "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6",
+ "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547",
+ "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9",
+ "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457",
+ "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8",
+ "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26",
+ "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5",
+ "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab",
+ "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070",
+ "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71",
+ "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9",
+ "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==11.1.0"
+ },
+ "platformdirs": {
+ "hashes": [
+ "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907",
+ "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==4.3.6"
+ },
+ "pyparsing": {
+ "hashes": [
+ "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1",
+ "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==3.2.1"
+ },
+ "pyqt6": {
+ "hashes": [
+ "sha256:3a4354816f11e812b727206a9ea6e79ff3774f1bb7228ad4b9318442d2c64ff9",
+ "sha256:452bae5840077bf0f146c798d7777f70d7bdd0c7dcfa9ee7a415c1daf2d10038",
+ "sha256:48bace7b87676bba5e6114482f3a20ca20be90c7f261b5d340464313f5f2bf5e",
+ "sha256:6d8628de4c2a050f0b74462e4c9cb97f839bf6ffabbca91711722ffb281570d9",
+ "sha256:8c5c05f5fdff31a5887dbc29b27615b09df467631238d7b449283809ffca6228",
+ "sha256:a9913d479f1ffee804bf7f232079baea4fb4b221a8f4890117588917a54ea30d",
+ "sha256:cf7123caea14e7ecf10bd12cae48e8d9970ef7caf627bc7d7988b0baa209adb3"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.9'",
+ "version": "==6.8.0"
+ },
+ "pyqt6-qt6": {
+ "hashes": [
+ "sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff",
+ "sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf",
+ "sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e",
+ "sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db",
+ "sha256:2f4b8b55b1414b93f340f22e8c88d25550efcdebc4b65a3927dd947b73bd4358",
+ "sha256:98aa99fe38ae68c5318284cd28f3479ba538c40bf6ece293980abae0925c1b24",
+ "sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287",
+ "sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515",
+ "sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3"
+ ],
+ "version": "==6.8.1"
+ },
+ "pyqt6-sip": {
+ "hashes": [
+ "sha256:14f95c6352e3b85dc26bf59cfbf77a470ecbd5fcdcf00af4b648f0e1b9eefb9e",
+ "sha256:15be741d1ae8c82bb7afe9a61f3cf8c50457f7d61229a1c39c24cd6e8f4d86dc",
+ "sha256:1d322ded1d1fea339cc6ac65b768e72c69c486eebb7db6ccde061b5786d74cc5",
+ "sha256:1ec52e962f54137a19208b6e95b6bd9f7a403eb25d7237768a99306cd9db26d1",
+ "sha256:1fb405615970e85b622b13b4cad140ff1e4182eb8334a0b27a4698e6217b89b0",
+ "sha256:22d66256b800f552ade51a463510bf905f3cb318aae00ff4288fae4de5d0e600",
+ "sha256:2ab85aaf155828331399c59ebdd4d3b0358e42c08250e86b43d56d9873df148a",
+ "sha256:3c269052c770c09b61fce2f2f9ea934a67dfc65f443d59629b4ccc8f89751890",
+ "sha256:5004514b08b045ad76425cf3618187091a668d972b017677b1b4b193379ef553",
+ "sha256:552ff8fdc41f5769d3eccc661f022ed496f55f6e0a214c20aaf56e56385d61b6",
+ "sha256:5643c92424fe62cb0b33378fef3d28c1525f91ada79e8a15bd9a05414a09503d",
+ "sha256:56ce0afb19cd8a8c63ff93ae506dffb74f844b88adaa6673ebc0dec43af48a76",
+ "sha256:57b5312ef13c1766bdf69b317041140b184eb24a51e1e23ce8fc5386ba8dffb2",
+ "sha256:5d7726556d1ca7a7ed78e19ba53285b64a2a8f6ad7ff4cb18a1832efca1a3102",
+ "sha256:69a879cfc94f4984d180321b76f52923861cd5bf4969aa885eef7591ee932517",
+ "sha256:6e6c1e2592187934f4e790c0c099d0033e986dcef7bdd3c06e3895ffa995e9fc",
+ "sha256:8b2ac36d6e04db6099614b9c1178a2f87788c7ffc3826571fb63d36ddb4c401d",
+ "sha256:8c207528992d59b0801458aa6fcff118e5c099608ef0fc6ff8bccbdc23f29c04",
+ "sha256:976c7758f668806d4df7a8853f390ac123d5d1f73591ed368bdb8963574ff589",
+ "sha256:accab6974b2758296400120fdcc9d1f37785b2ea2591f00656e1776f058ded6c",
+ "sha256:c1942e107b0243ced9e510d507e0f27aeea9d6b13e0a1b7c06fd52a62e0d41f7",
+ "sha256:c800db3464481e87b1d2b84523b075df1e8fc7856c6f9623dc243f89be1cb604",
+ "sha256:e996d320744ca8342cad6f9454345330d4f06bce129812d032bda3bad6967c5c",
+ "sha256:fa27b51ae4c7013b3700cf0ecf46907d1333ae396fc6511311920485cbce094b"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==13.9.1"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
+ "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "version": "==2.9.0.post0"
+ },
+ "s3transfer": {
+ "hashes": [
+ "sha256:3b39185cb72f5acc77db1a58b6e25b977f28d20496b6e58d6813d75f464d632f",
+ "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==0.11.2"
+ },
+ "scikit-learn": {
+ "hashes": [
+ "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691",
+ "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36",
+ "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f",
+ "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8",
+ "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2",
+ "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86",
+ "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322",
+ "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f",
+ "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107",
+ "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1",
+ "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35",
+ "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52",
+ "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33",
+ "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b",
+ "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb",
+ "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b",
+ "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5",
+ "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002",
+ "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b",
+ "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236",
+ "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d",
+ "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e",
+ "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422",
+ "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348",
+ "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e",
+ "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2",
+ "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1",
+ "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e",
+ "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97",
+ "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.9'",
+ "version": "==1.6.1"
+ },
+ "scipy": {
+ "hashes": [
+ "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6",
+ "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b",
+ "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e",
+ "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04",
+ "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f",
+ "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2",
+ "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85",
+ "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0",
+ "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692",
+ "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52",
+ "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c",
+ "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7",
+ "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b",
+ "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5",
+ "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea",
+ "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce",
+ "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4",
+ "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff",
+ "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2",
+ "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0",
+ "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf",
+ "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e",
+ "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df",
+ "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab",
+ "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364",
+ "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac",
+ "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e",
+ "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c",
+ "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d",
+ "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25",
+ "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95",
+ "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5",
+ "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0",
+ "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776",
+ "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef",
+ "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1",
+ "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9",
+ "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a",
+ "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54",
+ "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8"
+ ],
+ "markers": "python_version >= '3.10'",
+ "version": "==1.15.1"
+ },
+ "six": {
+ "hashes": [
+ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
+ "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "version": "==1.17.0"
+ },
+ "threadpoolctl": {
+ "hashes": [
+ "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107",
+ "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==3.5.0"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
+ "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==2.3.0"
+ }
+ },
+ "develop": {}
+}
diff --git a/digitaldog.jpg b/digitaldog.jpg
new file mode 100644
index 0000000..2a9ec62
Binary files /dev/null and b/digitaldog.jpg differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..680ea59
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+PyQt6
+numpy
+matplotlib
+scikit-learn
+boto3
+opencv-python
diff --git a/src/AWSRekognition.py b/src/AWSRekognition.py
new file mode 100755
index 0000000..d17689f
--- /dev/null
+++ b/src/AWSRekognition.py
@@ -0,0 +1,36 @@
+import boto3
+import cv2
+
+
+class AWSRekognition:
+ def __init__(self):
+ self.rekognition_client = boto3.client(
+ "rekognition", region_name="eu-central-1"
+ )
+
+ def label_image(self, img):
+ result = img.copy()
+ print("Detect Labels")
+ # Convert the image to bytes
+ _, image_bytes = cv2.imencode(".jpg", img)
+
+ # Call the detect_labels method
+ response = self.rekognition_client.detect_labels(
+ Image={"Bytes": image_bytes.tobytes()}, MaxLabels=20, MinConfidence=90
+ )
+
+ # Print the labels
+ for label in response["Labels"]:
+ print(label["Name"], label["Confidence"])
+
+ # Draw bounding boxes around the detected objects
+ for label in response["Labels"]:
+ for instance in label["Instances"]:
+ bounding_box = instance["BoundingBox"]
+ x = int(bounding_box["Left"] * result.shape[1])
+ y = int(bounding_box["Top"] * result.shape[0])
+ w = int(bounding_box["Width"] * result.shape[1])
+ h = int(bounding_box["Height"] * result.shape[0])
+ cv2.rectangle(result, (x, y), (x + w, y + h), (0, 255, 0), 2)
+
+ return result
diff --git a/src/ColorAnalysis.py b/src/ColorAnalysis.py
new file mode 100755
index 0000000..76f1051
--- /dev/null
+++ b/src/ColorAnalysis.py
@@ -0,0 +1,80 @@
+import cv2
+import numpy as np
+import matplotlib.pyplot as plt
+from mpl_toolkits.mplot3d import Axes3D
+from sklearn.cluster import KMeans
+
+
+class ColorAnalysis:
+
+ CLUSTERS = None
+ IMAGE = None
+ COLORS = None
+ LABELS = None
+
+ def __init__(self, img, clusters=3):
+ self.CLUSTERS = clusters
+ self.IMAGE = img
+
+ def show3DHistogram(self):
+ # img = Utilities.resize_image(img, 100)
+
+ # resize image# convert from BGR to RGB
+ # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+
+ # get rgb values from image to 1D array
+ r, g, b = cv2.split(self.IMAGE)
+ r = r.flatten()
+ g = g.flatten()
+ b = b.flatten()
+
+ # plotting
+ fig = plt.figure()
+ ax = Axes3D(fig)
+ ax.scatter(r, g, b)
+ plt.show()
+
+ def dominantColors(self):
+ img = self.IMAGE
+ # img = Utilities.resize_image(img, 100)
+ # reshaping to a list of pixels
+ img = img.reshape((img.shape[0] * img.shape[1], 3))
+
+ # using k-means to cluster pixels
+ kmeans = KMeans(n_clusters=self.CLUSTERS)
+ kmeans.fit(img)
+
+ # the cluster centers are our dominant colors.
+ self.COLORS = kmeans.cluster_centers_
+
+ # save labels
+ self.LABELS = kmeans.labels_
+
+ # returning after converting to integer from float
+ return self.COLORS.astype(int)
+
+ def rgb_to_hex(self, rgb):
+ return "#%02x%02x%02x" % (int(rgb[0]), int(rgb[1]), int(rgb[2]))
+
+ def plotClusters(self):
+ # plotting
+ fig = plt.figure()
+ ax = Axes3D(fig)
+ for label, pix in zip(self.LABELS, self.IMAGE):
+ ax.scatter(
+ pix[0], pix[1], pix[2], color=self.rgb_to_hex(self.COLORS[label])
+ )
+ plt.show()
+
+
+def create_visual_output(colors, width, height_max):
+ n_clusters = len(colors)
+ visual_output = 255 * np.ones((height_max, width, 3), np.uint8)
+ height = int(width / n_clusters)
+ box_width = int(width / n_clusters)
+ if height < height_max:
+ visual_output = 255 * np.ones((height, width, 3), np.uint8)
+
+ for i in range(n_clusters):
+ visual_output[:, box_width * i : box_width * (i + 1)] = colors[i]
+ return visual_output
diff --git a/src/HistogramManipulation.py b/src/HistogramManipulation.py
new file mode 100755
index 0000000..b39544c
--- /dev/null
+++ b/src/HistogramManipulation.py
@@ -0,0 +1,60 @@
+import cv2
+import numpy as np
+import Utilities
+
+
+# Task 1
+# function to stretch an image
+def stretchHistogram(img):
+ result = img.copy()
+ return result
+
+
+# Task 2
+# function to equalize an image
+def equalizeHistogram(img):
+ result = img.copy()
+ return result
+
+
+# Hilfsfunktion
+# function to apply a look-up table onto an image
+def applyLUT(img, LUT):
+ result = img.copy()
+ return result
+
+
+# Hilfsfunktion
+# function to find the minimum an maximum in a histogram
+def findMinMaxPos(histogram):
+ minPos = 0
+ maxPos = 255
+ return minPos, maxPos
+
+
+# Hilfsfunktion
+# function to create a vector containing the histogram
+def calculateHistogram(img, nrBins):
+ # create histogram vector
+ histogram = np.zeros([nrBins], dtype=int)
+ return histogram
+
+
+def apply_log(img):
+ result = img.copy()
+ return result
+
+
+def apply_exp(img):
+ result = img.copy()
+ return result
+
+
+def apply_inverse(img):
+ result = img.copy()
+ return result
+
+
+def apply_threshold(img, threshold):
+ result = img.copy()
+ return result
diff --git a/src/ImageFiltering.py b/src/ImageFiltering.py
new file mode 100644
index 0000000..0080547
--- /dev/null
+++ b/src/ImageFiltering.py
@@ -0,0 +1,67 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import datetime as dt
+import cv2
+import Utilities
+
+
+# apply median filter
+def applyMedianFilter(img, kSize):
+ filtered_img = img.copy()
+ return filtered_img
+
+
+# create a moving average kernel of arbitrary size
+def createMovingAverageKernel(kSize):
+ kernel = np.zeros((kSize, kSize))
+ return kernel
+
+
+def gaussian(x, y, sigmaX, sigmaY, meanX, meanY):
+ result = 1
+ return result
+
+
+# create a gaussian kernel of arbitrary size
+def createGaussianKernel(kSize, sigma=None):
+ kernel = np.zeros((kSize, kSize))
+ return kernel
+
+
+# create a sobel kernel in x direction of size 3x3
+def createSobelXKernel():
+ kernel = np.zeros((3, 3))
+ return kernel
+
+
+# create a sobel kernel in y direction of size 3x3
+def createSobelYKernel():
+ kernel = np.zeros((3, 3))
+ return kernel
+
+
+def applyKernelInSpatialDomain(img, kernel):
+ filtered_img = img.copy()
+ return filtered_img
+
+
+# Extra: create an integral image of the given image
+def createIntegralImage(img):
+ integral_image = img.copy()
+ return integral_image
+
+
+# Extra: apply the moving average filter by using an integral image
+def applyMovingAverageFilterWithIntegralImage(img, kSize):
+ filtered_img = img.copy()
+ return filtered_img
+
+
+# Extra:
+def applyMovingAverageFilterWithSeperatedKernels(img, kSize):
+ filtered_img = img.copy()
+ return filtered_img
+
+
+def run_runtime_evaluation(img):
+ pass
diff --git a/src/ImageInformation.py b/src/ImageInformation.py
new file mode 100644
index 0000000..dd15feb
--- /dev/null
+++ b/src/ImageInformation.py
@@ -0,0 +1,110 @@
+import numpy as np
+
+
+def luminanceD65(rgb: np.ndarray) -> np.float64:
+ """
+ Compute the luminance value of the specified linear RGB values
+ according to the D65 white point.
+
+ @param rgb(np.ndarray): sRGB image
+
+ @returns The luminance value
+ """
+ return np.float64(rgb @ [0.2126, 0.7152, 0.0722])
+
+
+# Task 1: Implement some kind of noticeable image manipulation in this function
+# e.g. channel manipulation, filter you already know, drawings on the image etc.
+def myFirstImageManipulation(img) -> np.ndarray:
+ """
+ Convert each pixel in the input image using the D65 luminance formula.
+
+ This function takes a NumPy array `img` as input and returns a new array with
+ each pixel value replaced by its corresponding D65 luminance value. The output
+ array maintains the same dimensions and data type as the input array, but each
+ pixel is processed through this specific transformation.
+
+ Parameters:
+ - img (ndarray): The input image data array of shape (height, width).
+
+ Returns:
+ - ndarray: An array where each pixel has been replaced by its D65
+ luminance value from the original image.
+ """
+ result = img.copy()
+
+ for x in range(0, result.shape[0]):
+ for y in range(0, result.shape[1]):
+
+ result[x][y] = luminanceD65(img[x][y])
+
+ return result
+
+
+# Task 2: Return the basic image properties to the console:
+# width, height,
+# the color of the first pixel of the image,
+# Color of the first pixel in the second row
+# Color of the first pixel in the second column
+# This function should work for images with three channels
+def imageSize(img: np.ndarray) -> list[int]:
+ """Get the height and width dimensions of an image array.
+
+ Args:
+ img (np.ndarray): A numpy array representing the image.
+
+ Returns:
+ list[int]: A two-element list containing [height, width] dimensions of the image.
+
+ Note:
+ The function works for any multi-dimensional numpy array. It returns
+ a list with the first two dimensions regardless of whether they are 2D or higher.
+ """
+ return [img.shape[0], img.shape[1]]
+
+
+def getPixelColor(img):
+ """
+ Retrieve color information from specific pixels in an image array.
+
+ This function processes an RGB image array and returns a list of color values
+ based on predefined positions. It is designed to handle multi-channel images,
+ extracting information such as first pixel of each row, specific channels,
+ and other notable points within the image data.
+
+ Parameters:
+ img (np.ndarray): The input image data array containing RGB or grayscale values.
+
+ Returns:
+ list: A structured list of color values extracted from different positions
+ in the image. This includes colors from specified rows, columns,
+ and channels where applicable.
+ """
+ return [img[0][1], img[1][0]]
+
+
+# Task 3: Separate the given channels of a colour image in this function and return it as separate image
+def returnChannel(img: np.ndarray, channel: int) -> np.ndarray:
+ """
+ Splits an image into a separate channel or keeps only the specified color channel.
+
+ Parameters:
+ - img: Input np.ndarray (required). This should be a color image array in RGB format.
+ If it's grayscale, this method might not work correctly as it expects three channels.
+ - channel: int (required). The index of the channel to keep. It can be 0 for red, 1 for green, or 2 for blue.
+
+ Returns:
+ - np.ndarray: A new image array where only the specified color channel is non-zero,
+ and other channels are zeroed out.
+ """
+ result = img.copy()
+
+ for x in range(0, result.shape[0]):
+ for y in range(0, result.shape[1]):
+ for c in range(0, 3):
+ if c == channel:
+ continue
+
+ result[x][y][c] = 0
+
+ return result
diff --git a/src/ImagePlotter.py b/src/ImagePlotter.py
new file mode 100755
index 0000000..bf2a0c7
--- /dev/null
+++ b/src/ImagePlotter.py
@@ -0,0 +1,262 @@
+from PyQt6.QtWidgets import QLabel, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
+from PyQt6.QtGui import QTransform
+from PyQt6.QtCore import Qt
+
+
+class ImagePlotter(QGraphicsView):
+ def __init__(self, parent=None):
+ super(ImagePlotter, self).__init__(parent)
+
+ self.scene = QGraphicsScene()
+ # self.setAlignment(Qt.AlignCenter)
+ self.zoom_level = 0
+
+ def show_image(self, image):
+ self.scene.clear()
+ item = QGraphicsPixmapItem(image)
+ self.scene.addItem(item)
+ self.setScene(self.scene)
+
+ scene_rect = self.scene.itemsBoundingRect()
+
+ self.fitInView(scene_rect, Qt.AspectRatioMode.KeepAspectRatio)
+ print(self.scene.sceneRect().width())
+ print(self.width())
+ self.show()
+
+ def wheelEvent(self, event):
+ # Get the current zoom level of the view
+ self.zoom_level = self.transform().m11()
+ print(self.zoom_level)
+ zoom = self.transform().m11()
+
+ # Calculate the new zoom level based on the direction of the mouse wheel
+ if event.angleDelta().y() > 0:
+ zoom *= 1.2
+ else:
+ zoom /= 1.2
+
+ # Set the new zoom level of the view
+ self.setTransform(QTransform().scale(zoom, zoom))
+
+
+class ImagePlotter2(QLabel):
+ def __init__(self, parent=None):
+ super(ImagePlotter2, self).__init__(parent)
+ self.setMouseTracking(True)
+ self.setCursor(Qt.CrossCursor)
+
+ # def mouseMoveEvent(self, event):
+ # cursor_pos = event.pos()
+ # print("Cursor position:", cursor_pos.x(), cursor_pos.y())
+
+ def mousePressEvent(self, event):
+ cursor_pos = event.pos()
+ pixmap = self.pixmap()
+ label_pos = self.pos()
+ pixmap_rect = pixmap.rect()
+
+ pixmap_pos = label_pos + pixmap_rect.topLeft()
+
+ pixel_color = pixmap.toImage().pixelColor(0, 0)
+ red = pixel_color.red()
+ green = pixel_color.green()
+ blue = pixel_color.blue()
+
+ print("RGB value of pixel at position (10, 10):", red, green, blue)
+
+ print(
+ "Position of pixmap top left:",
+ pixmap_rect.topLeft().x(),
+ pixmap_rect.topLeft().y(),
+ )
+ print("Position of pixmap in QLabel:", pixmap_pos.x(), pixmap_pos.y())
+ print("Label pos:", label_pos.x(), label_pos.y())
+ # image_pos = self.mapFrom(self., cursor_pos)
+
+
+# print("Mouse clicked at:", event.pos().x(), event.pos().y())
+# print("Mouse clicked at:", image_pos.x(), image_pos.y())
+
+
+from PyQt6.QtGui import QImage, QPixmap, QPainter
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+__author__ = "Atinderpal Singh"
+__license__ = "MIT"
+__version__ = "1.0"
+__email__ = "atinderpalap@gmail.com"
+
+
+class ImagePlotter3(QLabel):
+ """Basic image viewer class to show an image with zoom and pan functionaities.
+ Requirement: Qt's Qlabel widget name where the image will be drawn/displayed.
+ """
+
+ def __init__(self, parent=None):
+ super(ImagePlotter3, self).__init__(parent)
+ self.qlabel_image = (
+ QLabel()
+ ) # widget/window name where image is displayed (I'm usiing qlabel)
+ self.qimage_scaled = QImage() # scaled image to fit to the size of qlabel_image
+ self.qpixmap = QPixmap() # qpixmap to fill the qlabel_image
+
+ self.zoomX = 1 # zoom factor w.r.t size of qlabel_image
+ self.position = [
+ 0,
+ 0,
+ ] # position of top left corner of qimage_label w.r.t. qimage_scaled
+ self.panFlag = False # to enable or disable pan
+
+ self.qlabel_image.setSizePolicy(
+ QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored
+ )
+ self.__connectEvents()
+
+ def __connectEvents(self):
+ # Mouse events
+ self.qlabel_image.mousePressEvent = self.mousePressAction
+ self.qlabel_image.mouseMoveEvent = self.mouseMoveAction
+ self.qlabel_image.mouseReleaseEvent = self.mouseReleaseAction
+
+ def onResize(self):
+ """things to do when qlabel_image is resized"""
+ self.qpixmap = QPixmap(self.qlabel_image.size())
+ self.qpixmap.fill(QtCore.Qt.gray)
+ self.qimage_scaled = self.qimage.scaled(
+ self.qlabel_image.width() * self.zoomX,
+ self.qlabel_image.height() * self.zoomX,
+ QtCore.Qt.KeepAspectRatio,
+ )
+ self.update()
+
+ def loadImage(self, input_image):
+ """To load and display new image."""
+ self.qimage = input_image.toImage()
+ print(self.qimage.width())
+ self.qpixmap = QPixmap(self.qlabel_image.size())
+ if not self.qimage.isNull():
+ # reset Zoom factor and Pan position
+ self.zoomX = 1
+ self.position = [0, 0]
+ self.qimage_scaled = self.qimage.scaled(
+ self.qlabel_image.width(),
+ self.qlabel_image.height(),
+ QtCore.Qt.KeepAspectRatio,
+ )
+ self.update()
+ else:
+ self.statusbar.showMessage("Cannot open this image! Try another one.", 5000)
+
+ def update(self):
+ """This function actually draws the scaled image to the qlabel_image.
+ It will be repeatedly called when zooming or panning.
+ So, I tried to include only the necessary operations required just for these tasks.
+ """
+ if not self.qimage_scaled.isNull():
+ # check if position is within limits to prevent unbounded panning.
+ px, py = self.position
+ px = (
+ px
+ if (px <= self.qimage_scaled.width() - self.qlabel_image.width())
+ else (self.qimage_scaled.width() - self.qlabel_image.width())
+ )
+ py = (
+ py
+ if (py <= self.qimage_scaled.height() - self.qlabel_image.height())
+ else (self.qimage_scaled.height() - self.qlabel_image.height())
+ )
+ px = px if (px >= 0) else 0
+ py = py if (py >= 0) else 0
+ self.position = (px, py)
+
+ if self.zoomX == 1:
+ self.qpixmap.fill(QtCore.Qt.white)
+
+ # the act of painting the qpixamp
+ painter = QPainter()
+ painter.begin(self.qpixmap)
+ painter.drawImage(
+ QtCore.QPoint(0, 0),
+ self.qimage_scaled,
+ QtCore.QRect(
+ self.position[0],
+ self.position[1],
+ self.qlabel_image.width(),
+ self.qlabel_image.height(),
+ ),
+ )
+ painter.end()
+
+ self.qlabel_image.setPixmap(self.qpixmap)
+ else:
+ pass
+
+ def mousePressAction(self, QMouseEvent):
+ x, y = QMouseEvent.pos().x(), QMouseEvent.pos().y()
+ # print(x,y)
+ if self.panFlag:
+ self.pressed = QMouseEvent.pos() # starting point of drag vector
+ self.anchor = self.position # save the pan position when panning starts
+ print("Label pos:")
+
+ def mouseMoveAction(self, QMouseEvent):
+ x, y = QMouseEvent.pos().x(), QMouseEvent.pos().y()
+ if self.pressed:
+ dx, dy = (
+ x - self.pressed.x(),
+ y - self.pressed.y(),
+ ) # calculate the drag vector
+ self.position = (
+ self.anchor[0] - dx,
+ self.anchor[1] - dy,
+ ) # update pan position using drag vector
+ self.update()
+
+ # show the image with udated pan position
+
+ print("Label pos:")
+
+ def mouseReleaseAction(self, QMouseEvent):
+ self.pressed = None # clear the starting point of drag vector
+
+ def zoomPlus(self):
+ self.zoomX += 1
+ px, py = self.position
+ px += self.qlabel_image.width() / 2
+ py += self.qlabel_image.height() / 2
+ self.position = (px, py)
+ self.qimage_scaled = self.qimage.scaled(
+ self.qlabel_image.width() * self.zoomX,
+ self.qlabel_image.height() * self.zoomX,
+ QtCore.Qt.KeepAspectRatio,
+ )
+ self.update()
+
+ def zoomMinus(self):
+ if self.zoomX > 1:
+ self.zoomX -= 1
+ px, py = self.position
+ px -= self.qlabel_image.width() / 2
+ py -= self.qlabel_image.height() / 2
+ self.position = (px, py)
+ self.qimage_scaled = self.qimage.scaled(
+ self.qlabel_image.width() * self.zoomX,
+ self.qlabel_image.height() * self.zoomX,
+ QtCore.Qt.KeepAspectRatio,
+ )
+ self.update()
+
+ def resetZoom(self):
+ self.zoomX = 1
+ self.position = [0, 0]
+ self.qimage_scaled = self.qimage.scaled(
+ self.qlabel_image.width() * self.zoomX,
+ self.qlabel_image.height() * self.zoomX,
+ QtCore.Qt.KeepAspectRatio,
+ )
+ self.update()
+
+ def enablePan(self, value):
+ self.panFlag = value
diff --git a/src/MplWidget.py b/src/MplWidget.py
new file mode 100755
index 0000000..e43e391
--- /dev/null
+++ b/src/MplWidget.py
@@ -0,0 +1,60 @@
+# Imports
+from PyQt6 import QtWidgets
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas
+import matplotlib
+
+# Ensure using PyQt5 backend
+matplotlib.use("QT5Agg")
+
+
+# Matplotlib canvas class to create figure
+class MplCanvas(Canvas):
+ def __init__(self):
+ self.fig = Figure(figsize=(5, 2))
+ self.ax = self.fig.add_subplot(111)
+ Canvas.__init__(self, self.fig)
+ # Canvas.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ Canvas.updateGeometry(self)
+
+
+# Matplotlib widget
+class MplWidget(QtWidgets.QWidget):
+ _controller = None
+
+ def __init__(self, parent=None):
+ QtWidgets.QWidget.__init__(self, parent) # Inherit from QWidget
+ self.canvas = MplCanvas() # Create canvas object
+ self.vbl = QtWidgets.QVBoxLayout() # Set box for plotting
+ self.vbl.addWidget(self.canvas)
+ self.setLayout(self.vbl)
+
+ @property
+ def controller(self):
+ return self._image
+
+ @controller.setter
+ def controller(self, controller):
+ self._controller = controller
+
+ def drawHistogram(self, img):
+ self.canvas.ax.cla()
+
+ histogram = self._controller.calculate_histogram(img)
+ color = ("b", "g", "r")
+
+ if len(histogram) == 1:
+ color = "b"
+
+ for i, col in enumerate(color):
+ self.canvas.ax.plot(histogram[i], color=col)
+ self.canvas.ax.set_xlim([0, 256])
+ # self.canvas.figure.set_size_inches(2,2)
+
+ self.canvas.fig.tight_layout()
+ self.canvas.draw()
+
+ def save_histogram(self, str):
+ if self.canvas.fig is not None:
+ # Speichere die Figur als Bild
+ self.canvas.fig.savefig(str, dpi=300)
diff --git a/src/Playground_UI.py b/src/Playground_UI.py
new file mode 100755
index 0000000..fe924ca
--- /dev/null
+++ b/src/Playground_UI.py
@@ -0,0 +1,779 @@
+# Form implementation generated from reading ui file 'Playground_UI.ui'
+#
+# Created by: PyQt6 UI code generator 6.6.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ MainWindow.setObjectName("MainWindow")
+ MainWindow.resize(1259, 858)
+ self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.centralwidget.sizePolicy().hasHeightForWidth()
+ )
+ self.centralwidget.setSizePolicy(sizePolicy)
+ self.centralwidget.setObjectName("centralwidget")
+ self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.centralwidget)
+ self.horizontalLayout_5.setObjectName("horizontalLayout_5")
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setSizeConstraint(
+ QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint
+ )
+ self.horizontalLayout.setSpacing(7)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.layout_left = QtWidgets.QVBoxLayout()
+ self.layout_left.setObjectName("layout_left")
+ self.tab_widget_image = QtWidgets.QTabWidget(parent=self.centralwidget)
+ self.tab_widget_image.setMinimumSize(QtCore.QSize(500, 500))
+ self.tab_widget_image.setAutoFillBackground(True)
+ self.tab_widget_image.setObjectName("tab_widget_image")
+ self.tab_output_image = QtWidgets.QWidget()
+ self.tab_output_image.setAutoFillBackground(False)
+ self.tab_output_image.setObjectName("tab_output_image")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_output_image)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.label_output_image = QtWidgets.QLabel(parent=self.tab_output_image)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.MinimumExpanding,
+ QtWidgets.QSizePolicy.Policy.MinimumExpanding,
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.label_output_image.sizePolicy().hasHeightForWidth()
+ )
+ self.label_output_image.setSizePolicy(sizePolicy)
+ self.label_output_image.setFrameShape(QtWidgets.QFrame.Shape.Box)
+ self.label_output_image.setFrameShadow(QtWidgets.QFrame.Shadow.Plain)
+ self.label_output_image.setScaledContents(False)
+ self.label_output_image.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.label_output_image.setObjectName("label_output_image")
+ self.verticalLayout_3.addWidget(self.label_output_image)
+ self.pushButton_reset_output_image = QtWidgets.QPushButton(
+ parent=self.tab_output_image
+ )
+ self.pushButton_reset_output_image.setObjectName(
+ "pushButton_reset_output_image"
+ )
+ self.verticalLayout_3.addWidget(self.pushButton_reset_output_image)
+ self.pushButton_overwrite_input_image = QtWidgets.QPushButton(
+ parent=self.tab_output_image
+ )
+ self.pushButton_overwrite_input_image.setObjectName(
+ "pushButton_overwrite_input_image"
+ )
+ self.verticalLayout_3.addWidget(self.pushButton_overwrite_input_image)
+ self.tab_widget_image.addTab(self.tab_output_image, "")
+ self.tab_input_image = QtWidgets.QWidget()
+ self.tab_input_image.setObjectName("tab_input_image")
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_input_image)
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
+ self.label_input_image = QtWidgets.QLabel(parent=self.tab_input_image)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.MinimumExpanding,
+ QtWidgets.QSizePolicy.Policy.MinimumExpanding,
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.label_input_image.sizePolicy().hasHeightForWidth()
+ )
+ self.label_input_image.setSizePolicy(sizePolicy)
+ self.label_input_image.setAutoFillBackground(False)
+ self.label_input_image.setFrameShape(QtWidgets.QFrame.Shape.Box)
+ self.label_input_image.setScaledContents(False)
+ self.label_input_image.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.label_input_image.setObjectName("label_input_image")
+ self.verticalLayout_4.addWidget(self.label_input_image)
+ self.tab_widget_image.addTab(self.tab_input_image, "")
+ self.layout_left.addWidget(self.tab_widget_image)
+ self.line = QtWidgets.QFrame(parent=self.centralwidget)
+ self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.line.setObjectName("line")
+ self.layout_left.addWidget(self.line)
+ self.text_output = QtWidgets.QTextBrowser(parent=self.centralwidget)
+ self.text_output.setMinimumSize(QtCore.QSize(0, 150))
+ self.text_output.setObjectName("text_output")
+ self.layout_left.addWidget(
+ self.text_output, 0, QtCore.Qt.AlignmentFlag.AlignBottom
+ )
+ self.horizontalLayout.addLayout(self.layout_left)
+ self.vertical_line = QtWidgets.QFrame(parent=self.centralwidget)
+ self.vertical_line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
+ self.vertical_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.vertical_line.setObjectName("vertical_line")
+ self.horizontalLayout.addWidget(self.vertical_line)
+ self.layout_right = QtWidgets.QVBoxLayout()
+ self.layout_right.setObjectName("layout_right")
+ self.label = QtWidgets.QLabel(parent=self.centralwidget)
+ self.label.setObjectName("label")
+ self.layout_right.addWidget(self.label)
+ self.widget_histogram = MplWidget(parent=self.centralwidget)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.widget_histogram.sizePolicy().hasHeightForWidth()
+ )
+ self.widget_histogram.setSizePolicy(sizePolicy)
+ self.widget_histogram.setMinimumSize(QtCore.QSize(400, 250))
+ self.widget_histogram.setAutoFillBackground(True)
+ self.widget_histogram.setObjectName("widget_histogram")
+ self.layout_right.addWidget(self.widget_histogram)
+ self.horizontal_line = QtWidgets.QFrame(parent=self.centralwidget)
+ self.horizontal_line.setContextMenuPolicy(
+ QtCore.Qt.ContextMenuPolicy.PreventContextMenu
+ )
+ self.horizontal_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.horizontal_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.horizontal_line.setObjectName("horizontal_line")
+ self.layout_right.addWidget(self.horizontal_line)
+ self.tabWidget_controller = QtWidgets.QTabWidget(parent=self.centralwidget)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Expanding
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.tabWidget_controller.sizePolicy().hasHeightForWidth()
+ )
+ self.tabWidget_controller.setSizePolicy(sizePolicy)
+ self.tabWidget_controller.setMinimumSize(QtCore.QSize(400, 0))
+ self.tabWidget_controller.setObjectName("tabWidget_controller")
+ self.tab_image_analysis = QtWidgets.QWidget()
+ self.tab_image_analysis.setObjectName("tab_image_analysis")
+ self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.tab_image_analysis)
+ self.verticalLayout_7.setObjectName("verticalLayout_7")
+ self.tabWidget_analysis = QtWidgets.QTabWidget(parent=self.tab_image_analysis)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum,
+ QtWidgets.QSizePolicy.Policy.MinimumExpanding,
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.tabWidget_analysis.sizePolicy().hasHeightForWidth()
+ )
+ self.tabWidget_analysis.setSizePolicy(sizePolicy)
+ self.tabWidget_analysis.setMinimumSize(QtCore.QSize(380, 0))
+ self.tabWidget_analysis.setObjectName("tabWidget_analysis")
+ self.widget_image_information = QtWidgets.QWidget()
+ self.widget_image_information.setObjectName("widget_image_information")
+ self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.widget_image_information)
+ self.verticalLayout_8.setObjectName("verticalLayout_8")
+ self.widget = QtWidgets.QWidget(parent=self.widget_image_information)
+ self.widget.setObjectName("widget")
+ self.formLayout = QtWidgets.QFormLayout(self.widget)
+ self.formLayout.setObjectName("formLayout")
+ self.label_height_text = QtWidgets.QLabel(parent=self.widget)
+ self.label_height_text.setObjectName("label_height_text")
+ self.formLayout.setWidget(
+ 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_height_text
+ )
+ self.label_height_image = QtWidgets.QLabel(parent=self.widget)
+ self.label_height_image.setObjectName("label_height_image")
+ self.formLayout.setWidget(
+ 0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.label_height_image
+ )
+ self.label_width_text = QtWidgets.QLabel(parent=self.widget)
+ self.label_width_text.setObjectName("label_width_text")
+ self.formLayout.setWidget(
+ 1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_width_text
+ )
+ self.label_width_image = QtWidgets.QLabel(parent=self.widget)
+ self.label_width_image.setObjectName("label_width_image")
+ self.formLayout.setWidget(
+ 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.label_width_image
+ )
+ self.verticalLayout_8.addWidget(self.widget)
+ self.groupBox_2 = QtWidgets.QGroupBox(parent=self.widget_image_information)
+ self.groupBox_2.setObjectName("groupBox_2")
+ self.formLayout_4 = QtWidgets.QFormLayout(self.groupBox_2)
+ self.formLayout_4.setObjectName("formLayout_4")
+ self.label_4 = QtWidgets.QLabel(parent=self.groupBox_2)
+ self.label_4.setObjectName("label_4")
+ self.formLayout_4.setWidget(
+ 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_4
+ )
+ self.label_color_pixel1 = QtWidgets.QLabel(parent=self.groupBox_2)
+ self.label_color_pixel1.setObjectName("label_color_pixel1")
+ self.formLayout_4.setWidget(
+ 0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.label_color_pixel1
+ )
+ self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2)
+ self.label_5.setObjectName("label_5")
+ self.formLayout_4.setWidget(
+ 1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5
+ )
+ self.label_color_pixel2 = QtWidgets.QLabel(parent=self.groupBox_2)
+ self.label_color_pixel2.setObjectName("label_color_pixel2")
+ self.formLayout_4.setWidget(
+ 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.label_color_pixel2
+ )
+ self.verticalLayout_8.addWidget(self.groupBox_2)
+ self.groupBox = QtWidgets.QGroupBox(parent=self.widget_image_information)
+ self.groupBox.setObjectName("groupBox")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox)
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.pushButton_show_channel1 = QtWidgets.QPushButton(parent=self.groupBox)
+ self.pushButton_show_channel1.setObjectName("pushButton_show_channel1")
+ self.horizontalLayout_4.addWidget(self.pushButton_show_channel1)
+ self.pushButton_show_channel2 = QtWidgets.QPushButton(parent=self.groupBox)
+ self.pushButton_show_channel2.setObjectName("pushButton_show_channel2")
+ self.horizontalLayout_4.addWidget(self.pushButton_show_channel2)
+ self.pushButton_show_channel3 = QtWidgets.QPushButton(parent=self.groupBox)
+ self.pushButton_show_channel3.setObjectName("pushButton_show_channel3")
+ self.horizontalLayout_4.addWidget(self.pushButton_show_channel3)
+ self.verticalLayout_8.addWidget(self.groupBox)
+ self.pushButton_do_image_manipulation = QtWidgets.QPushButton(
+ parent=self.widget_image_information
+ )
+ self.pushButton_do_image_manipulation.setObjectName(
+ "pushButton_do_image_manipulation"
+ )
+ self.verticalLayout_8.addWidget(self.pushButton_do_image_manipulation)
+ spacerItem = QtWidgets.QSpacerItem(
+ 20,
+ 40,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ )
+ self.verticalLayout_8.addItem(spacerItem)
+ self.tabWidget_analysis.addTab(self.widget_image_information, "")
+ self.widget_color_analysis = QtWidgets.QWidget()
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.widget_color_analysis.sizePolicy().hasHeightForWidth()
+ )
+ self.widget_color_analysis.setSizePolicy(sizePolicy)
+ self.widget_color_analysis.setObjectName("widget_color_analysis")
+ self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.widget_color_analysis)
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
+ self.verticalLayout_color_analysis = QtWidgets.QVBoxLayout()
+ self.verticalLayout_color_analysis.setObjectName(
+ "verticalLayout_color_analysis"
+ )
+ self.label_color_analysis = QtWidgets.QLabel(parent=self.widget_color_analysis)
+ self.label_color_analysis.setMaximumSize(QtCore.QSize(16777215, 20))
+ self.label_color_analysis.setScaledContents(False)
+ self.label_color_analysis.setAlignment(
+ QtCore.Qt.AlignmentFlag.AlignLeading
+ | QtCore.Qt.AlignmentFlag.AlignLeft
+ | QtCore.Qt.AlignmentFlag.AlignTop
+ )
+ self.label_color_analysis.setObjectName("label_color_analysis")
+ self.verticalLayout_color_analysis.addWidget(self.label_color_analysis)
+ self.horizontalSlider_color_clusters = QtWidgets.QSlider(
+ parent=self.widget_color_analysis
+ )
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.horizontalSlider_color_clusters.sizePolicy().hasHeightForWidth()
+ )
+ self.horizontalSlider_color_clusters.setSizePolicy(sizePolicy)
+ self.horizontalSlider_color_clusters.setMinimumSize(QtCore.QSize(370, 0))
+ self.horizontalSlider_color_clusters.setSizeIncrement(QtCore.QSize(380, 0))
+ self.horizontalSlider_color_clusters.setMinimum(1)
+ self.horizontalSlider_color_clusters.setMaximum(10)
+ self.horizontalSlider_color_clusters.setSliderPosition(3)
+ self.horizontalSlider_color_clusters.setOrientation(
+ QtCore.Qt.Orientation.Horizontal
+ )
+ self.horizontalSlider_color_clusters.setTickPosition(
+ QtWidgets.QSlider.TickPosition.TicksBelow
+ )
+ self.horizontalSlider_color_clusters.setTickInterval(1)
+ self.horizontalSlider_color_clusters.setObjectName(
+ "horizontalSlider_color_clusters"
+ )
+ self.verticalLayout_color_analysis.addWidget(
+ self.horizontalSlider_color_clusters
+ )
+ self.line_2 = QtWidgets.QFrame(parent=self.widget_color_analysis)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.line_2.sizePolicy().hasHeightForWidth())
+ self.line_2.setSizePolicy(sizePolicy)
+ self.line_2.setMinimumSize(QtCore.QSize(370, 0))
+ self.line_2.setSizeIncrement(QtCore.QSize(370, 0))
+ self.line_2.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.line_2.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.line_2.setObjectName("line_2")
+ self.verticalLayout_color_analysis.addWidget(self.line_2)
+ self.label_image_color_analysis_output = QtWidgets.QLabel(
+ parent=self.widget_color_analysis
+ )
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.label_image_color_analysis_output.sizePolicy().hasHeightForWidth()
+ )
+ self.label_image_color_analysis_output.setSizePolicy(sizePolicy)
+ self.label_image_color_analysis_output.setMinimumSize(QtCore.QSize(370, 0))
+ self.label_image_color_analysis_output.setAlignment(
+ QtCore.Qt.AlignmentFlag.AlignCenter
+ )
+ self.label_image_color_analysis_output.setObjectName(
+ "label_image_color_analysis_output"
+ )
+ self.verticalLayout_color_analysis.addWidget(
+ self.label_image_color_analysis_output
+ )
+ self.verticalLayout_5.addLayout(self.verticalLayout_color_analysis)
+ self.tabWidget_analysis.addTab(self.widget_color_analysis, "")
+ self.verticalLayout_7.addWidget(self.tabWidget_analysis)
+ self.tabWidget_controller.addTab(self.tab_image_analysis, "")
+ self.tab_geometric_adjustments = QtWidgets.QWidget()
+ self.tab_geometric_adjustments.setObjectName("tab_geometric_adjustments")
+ self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_geometric_adjustments)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.lineEdit_image_width = QtWidgets.QLineEdit(
+ parent=self.tab_geometric_adjustments
+ )
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.lineEdit_image_width.sizePolicy().hasHeightForWidth()
+ )
+ self.lineEdit_image_width.setSizePolicy(sizePolicy)
+ self.lineEdit_image_width.setObjectName("lineEdit_image_width")
+ self.gridLayout_2.addWidget(self.lineEdit_image_width, 1, 1, 1, 1)
+ self.pushButton_adjust_image_size = QtWidgets.QPushButton(
+ parent=self.tab_geometric_adjustments
+ )
+ self.pushButton_adjust_image_size.setObjectName("pushButton_adjust_image_size")
+ self.gridLayout_2.addWidget(self.pushButton_adjust_image_size, 4, 0, 1, 2)
+ self.checkBox_fix_image_size = QtWidgets.QCheckBox(
+ parent=self.tab_geometric_adjustments
+ )
+ self.checkBox_fix_image_size.setChecked(True)
+ self.checkBox_fix_image_size.setObjectName("checkBox_fix_image_size")
+ self.gridLayout_2.addWidget(self.checkBox_fix_image_size, 2, 1, 1, 1)
+ self.label_3 = QtWidgets.QLabel(parent=self.tab_geometric_adjustments)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
+ self.lineEdit_image_height = QtWidgets.QLineEdit(
+ parent=self.tab_geometric_adjustments
+ )
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.lineEdit_image_height.sizePolicy().hasHeightForWidth()
+ )
+ self.lineEdit_image_height.setSizePolicy(sizePolicy)
+ self.lineEdit_image_height.setObjectName("lineEdit_image_height")
+ self.gridLayout_2.addWidget(self.lineEdit_image_height, 0, 1, 1, 1)
+ self.label_2 = QtWidgets.QLabel(parent=self.tab_geometric_adjustments)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1)
+ spacerItem1 = QtWidgets.QSpacerItem(
+ 20,
+ 40,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ )
+ self.gridLayout_2.addItem(spacerItem1, 5, 1, 1, 1)
+ self.tabWidget_controller.addTab(self.tab_geometric_adjustments, "")
+ self.tab_histogram_manip = QtWidgets.QWidget()
+ self.tab_histogram_manip.setObjectName("tab_histogram_manip")
+ self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.tab_histogram_manip)
+ self.verticalLayout_9.setObjectName("verticalLayout_9")
+ self.pushButton_hist_stretch = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_stretch.setObjectName("pushButton_hist_stretch")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_stretch)
+ self.pushButton_hist_equalization = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_equalization.setObjectName("pushButton_hist_equalization")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_equalization)
+ self.line_3 = QtWidgets.QFrame(parent=self.tab_histogram_manip)
+ self.line_3.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.line_3.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.line_3.setObjectName("line_3")
+ self.verticalLayout_9.addWidget(self.line_3)
+ self.pushButton_hist_log = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_log.setObjectName("pushButton_hist_log")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_log)
+ self.pushButton_hist_exp = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_exp.setObjectName("pushButton_hist_exp")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_exp)
+ self.pushButton_hist_inv = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_inv.setObjectName("pushButton_hist_inv")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_inv)
+ self.groupBox_6 = QtWidgets.QGroupBox(parent=self.tab_histogram_manip)
+ self.groupBox_6.setObjectName("groupBox_6")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_6)
+ self.verticalLayout.setContentsMargins(-1, 0, -1, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalSlider_hist_threshold = QtWidgets.QSlider(parent=self.groupBox_6)
+ self.horizontalSlider_hist_threshold.setMaximum(255)
+ self.horizontalSlider_hist_threshold.setProperty("value", 128)
+ self.horizontalSlider_hist_threshold.setOrientation(
+ QtCore.Qt.Orientation.Horizontal
+ )
+ self.horizontalSlider_hist_threshold.setTickPosition(
+ QtWidgets.QSlider.TickPosition.TicksBelow
+ )
+ self.horizontalSlider_hist_threshold.setTickInterval(0)
+ self.horizontalSlider_hist_threshold.setObjectName(
+ "horizontalSlider_hist_threshold"
+ )
+ self.verticalLayout.addWidget(self.horizontalSlider_hist_threshold)
+ self.verticalLayout_9.addWidget(self.groupBox_6)
+ self.pushButton_hist_fill = QtWidgets.QPushButton(
+ parent=self.tab_histogram_manip
+ )
+ self.pushButton_hist_fill.setObjectName("pushButton_hist_fill")
+ self.verticalLayout_9.addWidget(self.pushButton_hist_fill)
+ spacerItem2 = QtWidgets.QSpacerItem(
+ 20,
+ 40,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ )
+ self.verticalLayout_9.addItem(spacerItem2)
+ self.tabWidget_controller.addTab(self.tab_histogram_manip, "")
+ self.tab_filter = QtWidgets.QWidget()
+ self.tab_filter.setObjectName("tab_filter")
+ self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.tab_filter)
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
+ self.groupBox_3 = QtWidgets.QGroupBox(parent=self.tab_filter)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth())
+ self.groupBox_3.setSizePolicy(sizePolicy)
+ self.groupBox_3.setObjectName("groupBox_3")
+ self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.groupBox_3)
+ self.verticalLayout_11.setObjectName("verticalLayout_11")
+ self.widget_2 = QtWidgets.QWidget(parent=self.groupBox_3)
+ self.widget_2.setObjectName("widget_2")
+ self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.widget_2)
+ self.horizontalLayout_7.setContentsMargins(-1, 0, -1, 0)
+ self.horizontalLayout_7.setObjectName("horizontalLayout_7")
+ self.label_8 = QtWidgets.QLabel(parent=self.widget_2)
+ self.label_8.setObjectName("label_8")
+ self.horizontalLayout_7.addWidget(self.label_8)
+ self.spinBox_filter_avg_size = QtWidgets.QSpinBox(parent=self.widget_2)
+ self.spinBox_filter_avg_size.setReadOnly(False)
+ self.spinBox_filter_avg_size.setButtonSymbols(
+ QtWidgets.QAbstractSpinBox.ButtonSymbols.UpDownArrows
+ )
+ self.spinBox_filter_avg_size.setAccelerated(False)
+ self.spinBox_filter_avg_size.setCorrectionMode(
+ QtWidgets.QAbstractSpinBox.CorrectionMode.CorrectToNearestValue
+ )
+ self.spinBox_filter_avg_size.setKeyboardTracking(False)
+ self.spinBox_filter_avg_size.setProperty("showGroupSeparator", False)
+ self.spinBox_filter_avg_size.setMinimum(3)
+ self.spinBox_filter_avg_size.setSingleStep(2)
+ self.spinBox_filter_avg_size.setObjectName("spinBox_filter_avg_size")
+ self.horizontalLayout_7.addWidget(self.spinBox_filter_avg_size)
+ self.verticalLayout_11.addWidget(self.widget_2)
+ self.widget_3 = QtWidgets.QWidget(parent=self.groupBox_3)
+ self.widget_3.setObjectName("widget_3")
+ self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.widget_3)
+ self.horizontalLayout_6.setContentsMargins(-1, 0, -1, 0)
+ self.horizontalLayout_6.setObjectName("horizontalLayout_6")
+ self.pushButton_filter_movAvg = QtWidgets.QPushButton(parent=self.widget_3)
+ self.pushButton_filter_movAvg.setObjectName("pushButton_filter_movAvg")
+ self.horizontalLayout_6.addWidget(self.pushButton_filter_movAvg)
+ self.pushButton_filter_gauss = QtWidgets.QPushButton(parent=self.widget_3)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(
+ self.pushButton_filter_gauss.sizePolicy().hasHeightForWidth()
+ )
+ self.pushButton_filter_gauss.setSizePolicy(sizePolicy)
+ self.pushButton_filter_gauss.setObjectName("pushButton_filter_gauss")
+ self.horizontalLayout_6.addWidget(self.pushButton_filter_gauss)
+ self.pushButton_filter_median = QtWidgets.QPushButton(parent=self.widget_3)
+ self.pushButton_filter_median.setObjectName("pushButton_filter_median")
+ self.horizontalLayout_6.addWidget(self.pushButton_filter_median)
+ self.verticalLayout_11.addWidget(self.widget_3)
+ self.verticalLayout_6.addWidget(self.groupBox_3)
+ self.groupBox_4 = QtWidgets.QGroupBox(parent=self.tab_filter)
+ sizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Preferred
+ )
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.groupBox_4.sizePolicy().hasHeightForWidth())
+ self.groupBox_4.setSizePolicy(sizePolicy)
+ self.groupBox_4.setObjectName("groupBox_4")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox_4)
+ self.horizontalLayout_3.setContentsMargins(-1, 0, -1, 0)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.pushButton_filter_sobelX = QtWidgets.QPushButton(parent=self.groupBox_4)
+ self.pushButton_filter_sobelX.setObjectName("pushButton_filter_sobelX")
+ self.horizontalLayout_3.addWidget(self.pushButton_filter_sobelX)
+ self.pushButton_filter_sobelY = QtWidgets.QPushButton(parent=self.groupBox_4)
+ self.pushButton_filter_sobelY.setObjectName("pushButton_filter_sobelY")
+ self.horizontalLayout_3.addWidget(self.pushButton_filter_sobelY)
+ self.verticalLayout_6.addWidget(self.groupBox_4)
+ self.line_4 = QtWidgets.QFrame(parent=self.tab_filter)
+ self.line_4.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.line_4.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.line_4.setObjectName("line_4")
+ self.verticalLayout_6.addWidget(self.line_4)
+ self.groupBox_5 = QtWidgets.QGroupBox(parent=self.tab_filter)
+ self.groupBox_5.setObjectName("groupBox_5")
+ self.verticalLayout_10 = QtWidgets.QVBoxLayout(self.groupBox_5)
+ self.verticalLayout_10.setContentsMargins(-1, 0, -1, 0)
+ self.verticalLayout_10.setObjectName("verticalLayout_10")
+ self.pushButton_filter_movAvg_conv = QtWidgets.QPushButton(
+ parent=self.groupBox_5
+ )
+ self.pushButton_filter_movAvg_conv.setObjectName(
+ "pushButton_filter_movAvg_conv"
+ )
+ self.verticalLayout_10.addWidget(self.pushButton_filter_movAvg_conv)
+ self.pushButton_filter_movAvg_sep = QtWidgets.QPushButton(
+ parent=self.groupBox_5
+ )
+ self.pushButton_filter_movAvg_sep.setObjectName("pushButton_filter_movAvg_sep")
+ self.verticalLayout_10.addWidget(self.pushButton_filter_movAvg_sep)
+ self.pushButton_filter_movAvg_int = QtWidgets.QPushButton(
+ parent=self.groupBox_5
+ )
+ self.pushButton_filter_movAvg_int.setObjectName("pushButton_filter_movAvg_int")
+ self.verticalLayout_10.addWidget(self.pushButton_filter_movAvg_int)
+ self.line_5 = QtWidgets.QFrame(parent=self.groupBox_5)
+ self.line_5.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ self.line_5.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ self.line_5.setObjectName("line_5")
+ self.verticalLayout_10.addWidget(self.line_5)
+ self.pushButton_filter_evaluation = QtWidgets.QPushButton(
+ parent=self.groupBox_5
+ )
+ self.pushButton_filter_evaluation.setObjectName("pushButton_filter_evaluation")
+ self.verticalLayout_10.addWidget(self.pushButton_filter_evaluation)
+ self.verticalLayout_6.addWidget(self.groupBox_5)
+ spacerItem3 = QtWidgets.QSpacerItem(
+ 20,
+ 40,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ )
+ self.verticalLayout_6.addItem(spacerItem3)
+ self.tabWidget_controller.addTab(self.tab_filter, "")
+ self.tab_AI = QtWidgets.QWidget()
+ self.tab_AI.setObjectName("tab_AI")
+ self.formLayout_3 = QtWidgets.QFormLayout(self.tab_AI)
+ self.formLayout_3.setObjectName("formLayout_3")
+ self.pushButton_AWS_Labeling = QtWidgets.QPushButton(parent=self.tab_AI)
+ self.pushButton_AWS_Labeling.setObjectName("pushButton_AWS_Labeling")
+ self.formLayout_3.setWidget(
+ 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.pushButton_AWS_Labeling
+ )
+ self.tabWidget_controller.addTab(self.tab_AI, "")
+ self.layout_right.addWidget(self.tabWidget_controller)
+ self.horizontalLayout.addLayout(self.layout_right)
+ self.horizontalLayout_5.addLayout(self.horizontalLayout)
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1259, 37))
+ self.menubar.setObjectName("menubar")
+ self.menuMen = QtWidgets.QMenu(parent=self.menubar)
+ self.menuMen.setObjectName("menuMen")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
+ self.statusbar.setEnabled(True)
+ self.statusbar.setObjectName("statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+ self.actionBild_laden = QtGui.QAction(parent=MainWindow)
+ self.actionBild_laden.setObjectName("actionBild_laden")
+ self.actionBild_speichern = QtGui.QAction(parent=MainWindow)
+ self.actionBild_speichern.setObjectName("actionBild_speichern")
+ self.action_save_histogram = QtGui.QAction(parent=MainWindow)
+ self.action_save_histogram.setObjectName("action_save_histogram")
+ self.menuMen.addAction(self.actionBild_laden)
+ self.menuMen.addAction(self.actionBild_speichern)
+ self.menuMen.addAction(self.action_save_histogram)
+ self.menubar.addAction(self.menuMen.menuAction())
+
+ self.retranslateUi(MainWindow)
+ self.tab_widget_image.setCurrentIndex(0)
+ self.tabWidget_controller.setCurrentIndex(0)
+ self.tabWidget_analysis.setCurrentIndex(0)
+ QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+ def retranslateUi(self, MainWindow):
+ _translate = QtCore.QCoreApplication.translate
+ MainWindow.setWindowTitle(_translate("MainWindow", "Image Playground"))
+ self.label_output_image.setText(_translate("MainWindow", "Ausgabe"))
+ self.pushButton_reset_output_image.setText(
+ _translate("MainWindow", "Ausgabebild zurücksetzen")
+ )
+ self.pushButton_overwrite_input_image.setText(
+ _translate("MainWindow", "Eingabebild durch aktuelle Ausgabe ersetzen")
+ )
+ self.tab_widget_image.setTabText(
+ self.tab_widget_image.indexOf(self.tab_output_image),
+ _translate("MainWindow", "Ausgabe"),
+ )
+ self.label_input_image.setText(_translate("MainWindow", "Eingabe"))
+ self.tab_widget_image.setTabText(
+ self.tab_widget_image.indexOf(self.tab_input_image),
+ _translate("MainWindow", "Eingabe"),
+ )
+ self.label.setText(_translate("MainWindow", "Histogramm"))
+ self.label_height_text.setText(_translate("MainWindow", "Höhe:"))
+ self.label_height_image.setText(_translate("MainWindow", "0"))
+ self.label_width_text.setText(_translate("MainWindow", "Breite:"))
+ self.label_width_image.setText(_translate("MainWindow", "0"))
+ self.groupBox_2.setTitle(
+ _translate("MainWindow", "Farbinformation zu einzelnen Pixeln")
+ )
+ self.label_4.setText(_translate("MainWindow", "2. Reihe, 1. Spalte:"))
+ self.label_color_pixel1.setText(_translate("MainWindow", "[x,y,z]"))
+ self.label_5.setText(_translate("MainWindow", "1. Reihe, 2. Spalte:"))
+ self.label_color_pixel2.setText(_translate("MainWindow", "[x,y,z]"))
+ self.groupBox.setTitle(_translate("MainWindow", "Farbkanäle anzeigen"))
+ self.pushButton_show_channel1.setText(_translate("MainWindow", "Kanal 1"))
+ self.pushButton_show_channel2.setText(_translate("MainWindow", "Kanal 2"))
+ self.pushButton_show_channel3.setText(_translate("MainWindow", "Kanal 3"))
+ self.pushButton_do_image_manipulation.setText(
+ _translate("MainWindow", "Meine erste Bildmanipulation anwenden")
+ )
+ self.tabWidget_analysis.setTabText(
+ self.tabWidget_analysis.indexOf(self.widget_image_information),
+ _translate("MainWindow", "Bildinformation"),
+ )
+ self.label_color_analysis.setText(_translate("MainWindow", "Anzahl Cluster: "))
+ self.label_image_color_analysis_output.setText(
+ _translate("MainWindow", "Output")
+ )
+ self.tabWidget_analysis.setTabText(
+ self.tabWidget_analysis.indexOf(self.widget_color_analysis),
+ _translate("MainWindow", "Farbanalyse"),
+ )
+ self.tabWidget_controller.setTabText(
+ self.tabWidget_controller.indexOf(self.tab_image_analysis),
+ _translate("MainWindow", "Bildanalyse"),
+ )
+ self.lineEdit_image_width.setPlaceholderText(_translate("MainWindow", "100"))
+ self.pushButton_adjust_image_size.setText(
+ _translate("MainWindow", "Größe anpassen")
+ )
+ self.checkBox_fix_image_size.setText(
+ _translate("MainWindow", "Seitenverhältnis fixieren")
+ )
+ self.label_3.setText(_translate("MainWindow", "Neue Breite"))
+ self.lineEdit_image_height.setPlaceholderText(_translate("MainWindow", "100"))
+ self.label_2.setText(_translate("MainWindow", "Neue Höhe"))
+ self.tabWidget_controller.setTabText(
+ self.tabWidget_controller.indexOf(self.tab_geometric_adjustments),
+ _translate("MainWindow", "Größe"),
+ )
+ self.pushButton_hist_stretch.setText(
+ _translate("MainWindow", "Histogram Stretch")
+ )
+ self.pushButton_hist_equalization.setText(
+ _translate("MainWindow", "Histogram Equalization")
+ )
+ self.pushButton_hist_log.setText(_translate("MainWindow", "Logarithmus"))
+ self.pushButton_hist_exp.setText(
+ _translate("MainWindow", "Exponentialfunktion")
+ )
+ self.pushButton_hist_inv.setText(
+ _translate("MainWindow", "Histogramm invertieren")
+ )
+ self.groupBox_6.setTitle(_translate("MainWindow", "Threshold"))
+ self.pushButton_hist_fill.setText(
+ _translate("MainWindow", "Histogrammlücken füllen")
+ )
+ self.tabWidget_controller.setTabText(
+ self.tabWidget_controller.indexOf(self.tab_histogram_manip),
+ _translate("MainWindow", "Histogramm Mapping"),
+ )
+ self.groupBox_3.setTitle(_translate("MainWindow", "Weichzeichnungsfilter"))
+ self.label_8.setText(_translate("MainWindow", "Filtergröße:"))
+ self.pushButton_filter_movAvg.setText(
+ _translate("MainWindow", "Moving Average")
+ )
+ self.pushButton_filter_gauss.setText(_translate("MainWindow", "Gauß-Filter"))
+ self.pushButton_filter_median.setText(_translate("MainWindow", "Median-Filter"))
+ self.groupBox_4.setTitle(_translate("MainWindow", "Kantenerkennung"))
+ self.pushButton_filter_sobelX.setText(_translate("MainWindow", "Sobel X"))
+ self.pushButton_filter_sobelY.setText(_translate("MainWindow", "Sobel Y"))
+ self.groupBox_5.setTitle(
+ _translate("MainWindow", "Moving Average - Auswertung")
+ )
+ self.pushButton_filter_movAvg_conv.setText(_translate("MainWindow", "Faltung"))
+ self.pushButton_filter_movAvg_sep.setText(
+ _translate("MainWindow", "Separierbarer Filter")
+ )
+ self.pushButton_filter_movAvg_int.setText(
+ _translate("MainWindow", "Integralbild")
+ )
+ self.pushButton_filter_evaluation.setText(
+ _translate("MainWindow", "Auswertung durchführen")
+ )
+ self.tabWidget_controller.setTabText(
+ self.tabWidget_controller.indexOf(self.tab_filter),
+ _translate("MainWindow", "Filter"),
+ )
+ self.pushButton_AWS_Labeling.setText(
+ _translate("MainWindow", "AWS Rekognition Labeling")
+ )
+ self.tabWidget_controller.setTabText(
+ self.tabWidget_controller.indexOf(self.tab_AI),
+ _translate("MainWindow", "Recognition"),
+ )
+ self.menuMen.setTitle(_translate("MainWindow", "Menü"))
+ self.actionBild_laden.setText(_translate("MainWindow", "Bild laden"))
+ self.actionBild_speichern.setText(_translate("MainWindow", "Bild speichern"))
+ self.action_save_histogram.setText(
+ _translate("MainWindow", "Histogramm speichern")
+ )
+
+
+from MplWidget import MplWidget
diff --git a/src/Playground_UI.ui b/src/Playground_UI.ui
new file mode 100755
index 0000000..089f6fc
--- /dev/null
+++ b/src/Playground_UI.ui
@@ -0,0 +1,954 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1259
+ 858
+
+
+
+ Image Playground
+
+
+
+
+ 0
+ 0
+
+
+
+ -
+
+
+ 7
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
-
+
+
+
+ 500
+ 500
+
+
+
+ true
+
+
+ 0
+
+
+
+ false
+
+
+ Ausgabe
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+ Ausgabe
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Ausgabebild zurücksetzen
+
+
+
+ -
+
+
+ Eingabebild durch aktuelle Ausgabe ersetzen
+
+
+
+
+
+
+
+ Eingabe
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ QFrame::Box
+
+
+ Eingabe
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 150
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
-
+
+
+ Histogramm
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 250
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt::PreventContextMenu
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 0
+
+
+
+ 0
+
+
+
+ Bildanalyse
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 380
+ 0
+
+
+
+ 0
+
+
+
+ Bildinformation
+
+
+
-
+
+
+
-
+
+
+ Höhe:
+
+
+
+ -
+
+
+ 0
+
+
+
+ -
+
+
+ Breite:
+
+
+
+ -
+
+
+ 0
+
+
+
+
+
+
+ -
+
+
+ Farbinformation zu einzelnen Pixeln
+
+
+
-
+
+
+ 2. Reihe, 1. Spalte:
+
+
+
+ -
+
+
+ [x,y,z]
+
+
+
+ -
+
+
+ 1. Reihe, 2. Spalte:
+
+
+
+ -
+
+
+ [x,y,z]
+
+
+
+
+
+
+ -
+
+
+ Farbkanäle anzeigen
+
+
+
-
+
+
+ Kanal 1
+
+
+
+ -
+
+
+ Kanal 2
+
+
+
+ -
+
+
+ Kanal 3
+
+
+
+
+
+
+ -
+
+
+ Meine erste Bildmanipulation anwenden
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Farbanalyse
+
+
+ -
+
+
-
+
+
+
+ 16777215
+ 20
+
+
+
+ Anzahl Cluster:
+
+
+ false
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 370
+ 0
+
+
+
+
+ 380
+ 0
+
+
+
+ 1
+
+
+ 10
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 1
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 370
+ 0
+
+
+
+
+ 370
+ 0
+
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 370
+ 0
+
+
+
+ Output
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Größe
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 100
+
+
+
+ -
+
+
+ Größe anpassen
+
+
+
+ -
+
+
+ Seitenverhältnis fixieren
+
+
+ true
+
+
+
+ -
+
+
+ Neue Breite
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 100
+
+
+
+ -
+
+
+ Neue Höhe
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Histogramm Mapping
+
+
+ -
+
+
+ Histogram Stretch
+
+
+
+ -
+
+
+ Histogram Equalization
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Logarithmus
+
+
+
+ -
+
+
+ Exponentialfunktion
+
+
+
+ -
+
+
+ Histogramm invertieren
+
+
+
+ -
+
+
+ Threshold
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 255
+
+
+ 128
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 0
+
+
+
+
+
+
+ -
+
+
+ Histogrammlücken füllen
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Filter
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Weichzeichnungsfilter
+
+
+
-
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Filtergröße:
+
+
+
+ -
+
+
+ false
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ false
+
+
+ QAbstractSpinBox::CorrectToNearestValue
+
+
+ false
+
+
+ false
+
+
+ 3
+
+
+ 2
+
+
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Moving Average
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Gauß-Filter
+
+
+
+ -
+
+
+ Median-Filter
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Kantenerkennung
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Sobel X
+
+
+
+ -
+
+
+ Sobel Y
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Moving Average - Auswertung
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Faltung
+
+
+
+ -
+
+
+ Separierbarer Filter
+
+
+
+ -
+
+
+ Integralbild
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Auswertung durchführen
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Recognition
+
+
+ -
+
+
+ AWS Rekognition Labeling
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ Bild laden
+
+
+
+
+ Bild speichern
+
+
+
+
+ Histogramm speichern
+
+
+
+
+
+ MplWidget
+ QWidget
+
+ 1
+
+
+
+
+
diff --git a/src/Utilities.py b/src/Utilities.py
new file mode 100755
index 0000000..b189d61
--- /dev/null
+++ b/src/Utilities.py
@@ -0,0 +1,81 @@
+import cv2
+from matplotlib import pyplot as plt
+
+
+def showHistogram(img):
+ color = ("b", "g", "r")
+ if len(img.shape) == 2:
+ color = "b"
+
+ for i, col in enumerate(color):
+ histr = cv2.calcHist([img], [i], None, [256], [0, 256])
+ plt.plot(histr, color=col)
+ plt.xlim([0, 256])
+ plt.show()
+
+
+def calculate_histogram(img):
+ color = ("b", "g", "r")
+ histogram = [0, 0, 0]
+ if len(img.shape) == 2:
+ color = "b"
+ histogram = [0]
+
+ for i, col in enumerate(color):
+ histogram[i] = cv2.calcHist([img], [i], None, [256], [0, 256])
+
+ return histogram
+
+
+def plotHistogramVector(histogram):
+ plt.plot(histogram, color="b")
+ plt.xlim([0, len(histogram)])
+ plt.show()
+
+
+def resize_image(img, height):
+ aspect_ratio = img.shape[0] / img.shape[1]
+ dim = (height, int(height * aspect_ratio))
+ return cv2.resize(img, dim, interpolation=cv2.INTER_NEAREST)
+
+
+def resize_image(img, width, height, interpolation=cv2.INTER_NEAREST):
+ dim = (width, height)
+ return cv2.resize(img, dim, interpolation)
+
+
+def grabWebcam():
+ cap = cv2.VideoCapture(0)
+ while True:
+ ret, im = cap.read()
+ cv2.imshow("video test", im)
+ key = cv2.waitKey(10)
+ if key == 27:
+ break
+ # if key == ord(' '):
+ # cv2.imwrite('vid_result.jpg',im)
+
+
+def ensure_one_channel_grayscale_image(img):
+ if len(img.shape) == 2:
+ # Das Bild ist bereits in Graustufen
+ return img
+ elif len(img.shape) == 3 and img.shape[2] == 3:
+ # Das Bild ist farbig (3 Kanäle: BGR oder RGB)
+ return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+ else:
+ raise ValueError("Ungültiges Bildformat.")
+
+
+def ensure_three_channel_grayscale_image(gray_scale_img):
+ if len(gray_scale_img.shape) == 2:
+ img = cv2.cvtColor(gray_scale_img, cv2.COLOR_GRAY2BGR)
+ # Das Bild ist bereits in Graustufen
+ return img
+ elif len(gray_scale_img.shape) == 3 and gray_scale_img.shape[2] == 3:
+ # Das Bild ist farbig (3 Kanäle: BGR oder RGB)
+ img = cv2.cvtColor(gray_scale_img, cv2.COLOR_BGR2GRAY)
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
+ return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+ else:
+ raise ValueError("Ungültiges Bildformat.")
diff --git a/src/app.py b/src/app.py
new file mode 100755
index 0000000..ab6932a
--- /dev/null
+++ b/src/app.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+import sys
+from models import ImageModel
+from views import MainView
+from controllers import MainController
+from PyQt6.QtWidgets import QApplication
+
+
+class App(QApplication):
+ def __init__(self, sys_argv):
+ super(App, self).__init__(sys_argv)
+ self.model = ImageModel()
+ self.main_controller = MainController(self.model)
+ self.main_view = MainView(self.model, self.main_controller)
+ self.main_view.show()
+
+
+if __name__ == "__main__":
+ app = App(sys.argv)
+ sys.exit(app.exec())
diff --git a/src/controllers.py b/src/controllers.py
new file mode 100755
index 0000000..c95fe75
--- /dev/null
+++ b/src/controllers.py
@@ -0,0 +1,148 @@
+import logging
+from loggers import LogEmitter
+
+import cv2
+import numpy as np
+import Utilities
+import ColorAnalysis as CA
+import HistogramManipulation as HM
+import AWSRekognition as AI
+import ImageInformation as II
+import ImageFiltering as IF
+
+
+class MainController:
+ def __init__(self, model):
+ super().__init__()
+ self._model = model
+
+ self.logger = logging.getLogger()
+ self.log_handler = LogEmitter()
+ self.logger.addHandler(self.log_handler)
+
+ # Set the log level to INFO
+ self.logger.setLevel(logging.INFO)
+
+ def test_function(self):
+ print("Test function")
+ pass
+
+ def loadImage(self, str):
+ img = cv2.imread(str, 1)
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+ self._model.input_image = img
+ self.logger.info("Image loaded: " + str)
+
+ def saveImage(self, str):
+ img = cv2.cvtColor(self._model.image, cv2.COLOR_RGB2BGR)
+ cv2.imwrite(str, img)
+ self.logger.info("Image written to: " + str)
+
+ #
+ def changeImage(self):
+ image = np.zeros((256, 256, 3), np.uint8)
+ image[0 : 256 // 2, :] = (255, 0, 0)
+ image[256 // 2 : 256, :] = (0, 0, 255)
+ Utilities.resize_image(image, 100)
+ self._model.input_image = image
+
+ def set_image_as_input_image(self):
+ self._model.input_image = self._model.image.copy()
+
+ def reset_output_image(self):
+ self._model.image = self._model.input_image
+
+ def resize_image(self, new_width, new_height):
+ self._model.image = Utilities.resize_image(
+ self._model.input_image, new_width, new_height
+ )
+
+ def analyseColors(self, ncluster):
+ color_analyzer = CA.ColorAnalysis(self._model.image, ncluster)
+ return color_analyzer.dominantColors()
+
+ def create_visual_output(self, colors, width, height_max):
+ return CA.create_visual_output(colors, width, height_max)
+
+ def calculate_histogram(self, img):
+ return Utilities.calculate_histogram(img)
+
+ def label_image(self):
+ ai = AI.AWSRekognition()
+ self._model.image = ai.label_image(self._model.input_image)
+ self.logger.critical("Labeling successfull")
+
+ #####################################
+ # Übung 1
+ #####################################
+
+ def get_image_information(self):
+ return II.imageSize(self._model.image)
+
+ def get_pixel_information(self):
+ return II.getPixelColor(self._model.image)
+
+ def show_channel(self, channel):
+ self._model.image = II.returnChannel(self._model.input_image, channel)
+
+ def do_first_image_manipulation(self):
+ self._model.image = II.myFirstImageManipulation(self._model.image)
+
+ #####################################
+ # Übung 2
+ #####################################
+
+ def stretch_image(self):
+ self._model.image = HM.stretchHistogram(self._model.input_image)
+
+ def equalize_image(self):
+ self._model.image = HM.equalizeHistogram(self._model.input_image)
+
+ def apply_log(self):
+ self._model.image = HM.apply_log(self._model.input_image)
+
+ def apply_exp(self):
+ self._model.image = HM.apply_exp(self._model.input_image)
+
+ def apply_inv(self):
+ self._model.image = HM.apply_inverse(self._model.input_image)
+
+ def apply_threshold(self, threshold):
+ self._model.image = HM.apply_threshold(self._model.input_image, threshold)
+
+ #####################################
+ # Übung 3
+ #####################################
+
+ def apply_gaussian_filter(self, kernel_size):
+ kernel = IF.createGaussianKernel(kernel_size)
+ img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def apply_moving_avg_filter(self, kernel_size):
+ kernel = IF.createMovingAverageKernel(kernel_size)
+ img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def apply_moving_avg_filter_integral(self, kernel_size):
+ img = IF.applyMovingAverageFilterWithIntegralImage(
+ self._model.input_image, kernel_size
+ )
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def apply_median_filter(self, kernel_size):
+ img = IF.applyMedianFilter(self._model.input_image, kernel_size)
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def apply_filter_sobelX(self):
+ kernel = IF.createSobelXKernel()
+ img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def apply_filter_sobelY(self):
+ kernel = IF.createSobelYKernel()
+ img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
+ self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
+
+ def run_runtime_evaluation(self):
+ IF.run_runtime_evaluation(self._model.input_image)
diff --git a/src/loggers.py b/src/loggers.py
new file mode 100755
index 0000000..642e8c4
--- /dev/null
+++ b/src/loggers.py
@@ -0,0 +1,42 @@
+import logging
+from PyQt6.QtCore import pyqtSignal, QObject
+import html
+
+
+class LogEmitter(QObject, logging.Handler):
+ """A custom logging handler that emits a signal with the log message."""
+
+ messageEmitted = pyqtSignal(str)
+
+ def __init__(self):
+ super().__init__()
+
+ # Define color codes
+ self.color_codes = {
+ logging.DEBUG: "gray",
+ logging.INFO: "black",
+ logging.WARNING: "orange",
+ logging.ERROR: "red",
+ logging.CRITICAL: "darkred",
+ }
+
+ self.level_names = {
+ logging.DEBUG: "DEBUG",
+ logging.INFO: "INFO",
+ logging.WARNING: "WARNING",
+ logging.ERROR: "ERROR",
+ logging.CRITICAL: "CRITICAL",
+ }
+
+ def emit(self, record):
+ msg = self.format(record)
+
+ # Get color code and level name based on log level
+ level_color = self.color_codes.get(record.levelno, "black")
+ level_name = self.level_names.get(record.levelno, "UNKNOWN")
+
+ # Create HTML string with color code and level name
+ html_msg = f'{level_name}: {html.escape(msg)}'
+
+ # Emit signal with HTML message
+ self.messageEmitted.emit(html_msg)
diff --git a/src/models.py b/src/models.py
new file mode 100755
index 0000000..3b30574
--- /dev/null
+++ b/src/models.py
@@ -0,0 +1,61 @@
+import numpy as np
+import Utilities
+import cv2
+
+from PyQt6.QtCore import QObject, pyqtSignal
+
+
+class ImageModel(QObject):
+ image_changed = pyqtSignal(np.ndarray)
+ input_image_changed = pyqtSignal(np.ndarray)
+
+ _image = None # output image
+ _input_image = None # input image (backup)
+
+ def __init__(self):
+ super().__init__()
+ self.initialize()
+
+ @property
+ def image(self):
+ return self._image
+
+ @property
+ def input_image(self):
+ return self._input_image
+
+ @image.setter
+ def image(self, img):
+ self._image = img.copy()
+ print("M: image changed!")
+ # update in model is reflected in view by sending a signal to view
+ self.image_changed.emit(img)
+
+ @input_image.setter
+ def input_image(self, img):
+ self._input_image = img.copy()
+ self.image = img
+ self.input_image_changed.emit(img)
+
+ def initialize(self):
+ image = np.zeros((256, 256, 3), np.uint8)
+ image[:, 0 : 256 // 2] = (255, 0, 0)
+ image[:, 256 // 2 : 256] = (0, 0, 255)
+ self.input_image = image
+
+ class ColorAnalysis:
+
+ _clusters = None
+ _image = None
+ _colors = None
+ _labels = None
+
+ def __init__(self, img, clusters=3):
+ self.CLUSTERS = clusters
+ self.IMAGE = Utilities.resize_image(img, 100)
+ cv2.imshow("Input", self.IMAGE)
+
+ def load_rgb_image(self, path):
+ image = cv2.imread(path, 1)
+ # image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
+ self.image = image
diff --git a/src/views.py b/src/views.py
new file mode 100755
index 0000000..1e37e4b
--- /dev/null
+++ b/src/views.py
@@ -0,0 +1,383 @@
+from PyQt6 import QtGui
+from PyQt6.QtCore import Qt, QRegularExpression
+from PyQt6.QtWidgets import QMainWindow
+from PyQt6.QtGui import QPixmap, QRegularExpressionValidator
+
+
+from PyQt6.QtCore import pyqtSlot
+
+from PyQt6.QtWidgets import QFileDialog
+
+
+from Playground_UI import Ui_MainWindow
+
+
+class MainView(QMainWindow):
+ def __init__(self, model, main_controller):
+ super().__init__()
+ self._model = model
+ self._main_controller = main_controller
+ self._ui = Ui_MainWindow()
+ self._ui.setupUi(self)
+ self.update_image()
+ self.update_input_image()
+
+ self._ui.widget_histogram.controller = self._main_controller
+
+ ####################
+ # Input validators
+ ####################
+ # Create a regular expression that matches integer values between 1 and infinity
+ regex = QRegularExpression("[1-9][0-9]*")
+ # Create a validator based on the regular expression
+ validator = QRegularExpressionValidator(regex)
+ # Set the validator for the line edit widget
+ self._ui.lineEdit_image_height.setValidator(validator)
+ self._ui.lineEdit_image_width.setValidator(validator)
+
+ ####################################################################
+ # connect widgets to controllers
+ ####################################################################
+ # open file buttons
+ # self._ui.pushButton.clicked.connect(self._main_controller.test_function)
+ self._ui.actionBild_laden.triggered.connect(self.on_open_image_from_filesystem)
+ self._ui.actionBild_speichern.triggered.connect(
+ self.on_save_image_to_filesystem
+ )
+ self._ui.action_save_histogram.triggered.connect(
+ self.on_save_histogram_to_filesystem
+ )
+ self._ui.horizontalSlider_color_clusters.sliderReleased.connect(
+ self.on_color_cluster_slider_changed
+ )
+ self._ui.pushButton_hist_stretch.clicked.connect(
+ self.on_hist_stretch_button_clicked
+ )
+ self._ui.pushButton_hist_equalization.clicked.connect(
+ self.on_hist_equal_button_clicked
+ )
+ self._ui.pushButton_AWS_Labeling.clicked.connect(
+ self.on_AWS_Rekognition_button_clicked
+ )
+ self._ui.pushButton_adjust_image_size.clicked.connect(
+ self.on_resize_button_clicked
+ )
+
+ self._ui.pushButton_reset_output_image.clicked.connect(
+ self.on_reset_output_image_button_clicked
+ )
+ self._ui.pushButton_overwrite_input_image.clicked.connect(
+ self.on_overwrite_input_image_button_clicked
+ )
+ self._ui.lineEdit_image_height.editingFinished.connect(
+ self.on_new_image_height_requested
+ )
+ self._ui.lineEdit_image_width.editingFinished.connect(
+ self.on_new_image_width_requested
+ )
+
+ #########
+ # Buttons für Übung
+ #########
+ self._ui.pushButton_show_channel1.clicked.connect(
+ self.on_channel_1_button_clicked
+ )
+ self._ui.pushButton_show_channel2.clicked.connect(
+ self.on_channel_2_button_clicked
+ )
+ self._ui.pushButton_show_channel3.clicked.connect(
+ self.on_channel_3_button_clicked
+ )
+ self._ui.pushButton_do_image_manipulation.clicked.connect(
+ self.on_do_first_image_manipulation_button_clicked
+ )
+
+ self._ui.pushButton_hist_log.clicked.connect(
+ self.on_apply_log_on_hist_button_clicked
+ )
+ self._ui.pushButton_hist_exp.clicked.connect(
+ self.on_apply_exp_on_hist_button_clicked
+ )
+ self._ui.pushButton_hist_inv.clicked.connect(
+ self.on_apply_inverse_on_hist_button_clicked
+ )
+ self._ui.horizontalSlider_hist_threshold.sliderReleased.connect(
+ self.on_apply_threshold_on_hist_button_clicked
+ )
+
+ self._ui.pushButton_filter_sobelX.clicked.connect(
+ self.on_filter_sobelX_button_clicked
+ )
+ self._ui.pushButton_filter_sobelY.clicked.connect(
+ self.on_filter_sobelY_button_clicked
+ )
+ self._ui.pushButton_filter_gauss.clicked.connect(
+ self.on_filter_gauss_button_clicked
+ )
+ self._ui.pushButton_filter_movAvg.clicked.connect(
+ self.on_filter_moving_avg_button_clicked
+ )
+ self._ui.pushButton_filter_movAvg_int.clicked.connect(
+ self.on_filter_moving_avg_integral_button_clicked
+ )
+ self._ui.pushButton_filter_median.clicked.connect(
+ self.on_filter_median_button_clicked
+ )
+ self._ui.pushButton_filter_evaluation.clicked.connect(
+ self.on_runtime_evaluation_button_clicked
+ )
+
+ ####################################################################
+ # listen for model event signals
+ ####################################################################
+ # file name is updated
+ self._model.image_changed.connect(self.on_image_changed)
+ self._model.input_image_changed.connect(self.on_input_image_changed)
+
+ ###################
+ # Connect Logging
+ ###################
+
+ self._main_controller.log_handler.messageEmitted.connect(self.add_log_message)
+
+ def show(self):
+ super().show()
+ self.on_input_image_changed()
+
+ @pyqtSlot(str)
+ def add_log_message(self, msg):
+ """Add a log message to the QTextBrowser widget."""
+ self._ui.text_output.append(msg)
+
+ def resizeEvent(self, a0):
+ self.on_input_image_changed()
+ QMainWindow.resizeEvent(self, a0)
+
+ def on_open_image_from_filesystem(self):
+ fname = QFileDialog.getOpenFileName(
+ self, "Open file", "../", "Image Files (*.png *.jpg *.bmp)"
+ )
+ self._main_controller.loadImage(fname[0])
+ print(fname[0])
+
+ def on_save_image_to_filesystem(self):
+ fname, _ = QFileDialog.getSaveFileName(
+ self, "Save file", "../", "Image Files (*.png *.jpg *.bmp)"
+ )
+ if fname:
+ self._main_controller.saveImage(fname)
+
+ def on_save_histogram_to_filesystem(self):
+ fname, _ = QFileDialog.getSaveFileName(
+ self, "Save file", "../", "Image Files (*.png *.jpg *.bmp)"
+ )
+ if fname:
+ self._ui.widget_histogram.save_histogram(fname)
+
+ def on_image_changed(self):
+ self.update_image()
+ self.update_histogram()
+ self.update_image_information()
+
+ def on_input_image_changed(self):
+ self.update_input_image()
+ self.on_image_changed()
+
+ def on_image_mouse_pressed(self):
+ self._main_controller.logger.logger.critical("Mouse pressed")
+
+ def on_overwrite_input_image_button_clicked(self):
+ self._main_controller.set_image_as_input_image()
+
+ def on_reset_output_image_button_clicked(self):
+ self._main_controller.reset_output_image()
+
+ def on_new_image_height_requested(self):
+ if self._ui.checkBox_fix_image_size.isChecked():
+ image_size = self._main_controller.get_image_information()
+ aspect_ratio = image_size[0] / image_size[1]
+ # dim = (height, int(height * aspect_ratio))
+ self._ui.lineEdit_image_width.setText(
+ str(int(int(self._ui.lineEdit_image_height.text()) / aspect_ratio))
+ )
+ pass
+
+ def on_new_image_width_requested(self):
+ if self._ui.checkBox_fix_image_size.isChecked():
+ image_size = self._main_controller.get_image_information()
+ aspect_ratio = image_size[0] / image_size[1]
+ # dim = (height, int(height * aspect_ratio))
+ self._ui.lineEdit_image_height.setText(
+ str(int(int(self._ui.lineEdit_image_width.text()) * aspect_ratio))
+ )
+ pass
+
+ def on_color_cluster_slider_changed(self):
+ dominant_colors = self._main_controller.analyseColors(
+ self._ui.horizontalSlider_color_clusters.sliderPosition()
+ )
+ self.update_color_cluster_output(dominant_colors)
+ print(self._ui.horizontalSlider_color_clusters.sliderPosition())
+
+ def on_hist_stretch_button_clicked(self):
+ self._main_controller.stretch_image()
+ self.on_image_changed()
+
+ def on_hist_equal_button_clicked(self):
+ self._main_controller.equalize_image()
+ self.on_image_changed()
+
+ def on_AWS_Rekognition_button_clicked(self):
+ self._main_controller.label_image()
+
+ def update_color_cluster_output(self, dominant_colors):
+ size = self._ui.label_image_color_analysis_output.size()
+ visual_output = self._main_controller.create_visual_output(
+ dominant_colors, size.width(), size.height()
+ )
+ qt_img = convert_cv_qt(visual_output, size.width(), size.height())
+ self._ui.label_image_color_analysis_output.setPixmap(qt_img)
+
+ def on_resize_button_clicked(self):
+ self._main_controller.resize_image(
+ int(self._ui.lineEdit_image_width.text()),
+ int(self._ui.lineEdit_image_height.text()),
+ )
+ # self.on_image_changed()
+
+ def update_image(self):
+ frame = self._model.image
+ size = self._ui.label_output_image.size()
+ # qt_img = convert_cv_qt(frame, size.width(), size.height())
+ qt_img = convert_cv2scaledqt(frame, size.width(), size.height())
+ # self._ui.label_output_image.loadImage(qt_img)
+ self._ui.label_output_image.setPixmap(qt_img)
+
+ def update_input_image(self):
+ frame = self._model.input_image
+ size = self._ui.label_output_image.size()
+ qt_img = convert_cv2scaledqt(frame, size.width(), size.height())
+ # qt_img = convert_cv_qt(frame, size.width(), size.height())
+ self._ui.label_input_image.setPixmap(qt_img)
+
+ def update_histogram(self):
+ self._ui.widget_histogram.drawHistogram(self._model.image)
+
+ def update_image_information(self):
+ image_size = self._main_controller.get_image_information()
+ self._ui.label_height_image.setText(str(image_size[0]))
+ self._ui.label_width_image.setText(str(image_size[1]))
+
+ self._ui.lineEdit_image_height.setText(str(image_size[0]))
+ self._ui.lineEdit_image_width.setText(str(image_size[1]))
+
+ pixel_colors = self._main_controller.get_pixel_information()
+ self._ui.label_color_pixel1.setText(str(pixel_colors[0]))
+ self._ui.label_color_pixel2.setText(str(pixel_colors[1]))
+
+ #####################
+ # Übung 1
+ #####################
+
+ def on_channel_1_button_clicked(self):
+ self._main_controller.show_channel(0)
+ self.on_image_changed()
+
+ def on_channel_2_button_clicked(self):
+ self._main_controller.show_channel(1)
+ self.on_image_changed()
+
+ def on_channel_3_button_clicked(self):
+ self._main_controller.show_channel(2)
+ self.on_image_changed()
+
+ def on_do_first_image_manipulation_button_clicked(self):
+ self._main_controller.do_first_image_manipulation()
+ self.on_image_changed()
+
+ #####################
+ # Übung 2
+ #####################
+
+ def on_apply_log_on_hist_button_clicked(self):
+ self._main_controller.apply_log()
+ self.on_image_changed()
+
+ def on_apply_exp_on_hist_button_clicked(self):
+ self._main_controller.apply_exp()
+ self.on_image_changed()
+
+ def on_apply_inverse_on_hist_button_clicked(self):
+ self._main_controller.apply_inv()
+ self.on_image_changed()
+
+ def on_apply_threshold_on_hist_button_clicked(self):
+ self._main_controller.apply_threshold(
+ self._ui.horizontalSlider_hist_threshold.sliderPosition()
+ )
+ self.on_image_changed()
+
+ #####################
+ # Übung 3
+ #####################
+
+ def on_filter_sobelX_button_clicked(self):
+ self._main_controller.apply_filter_sobelX()
+ self.on_image_changed()
+
+ def on_filter_sobelY_button_clicked(self):
+ self._main_controller.apply_filter_sobelY()
+ self.on_image_changed()
+
+ def on_filter_gauss_button_clicked(self):
+ self._main_controller.apply_gaussian_filter(
+ self._ui.spinBox_filter_avg_size.value()
+ )
+ self.on_image_changed()
+
+ def on_filter_moving_avg_button_clicked(self):
+ self._main_controller.apply_moving_avg_filter(
+ self._ui.spinBox_filter_avg_size.value()
+ )
+ self.on_image_changed()
+
+ def on_filter_moving_avg_integral_button_clicked(self):
+ self._main_controller.apply_moving_avg_filter_integral(
+ self._ui.spinBox_filter_avg_size.value()
+ )
+ self.on_image_changed()
+
+ def on_filter_median_button_clicked(self):
+ self._main_controller.apply_median_filter(
+ self._ui.spinBox_filter_avg_size.value()
+ )
+ self.on_image_changed()
+
+ def on_runtime_evaluation_button_clicked(self):
+ self._main_controller.run_runtime_evaluation()
+
+
+def convert_cv_qt(cv_img, display_width, display_height):
+ """Convert from an opencv image to QPixmap"""
+ h, w, ch = cv_img.shape
+ bytes_per_line = ch * w
+ convert_to_Qt_format = QtGui.QImage(
+ cv_img.data, w, h, bytes_per_line, QtGui.QImage.Format.Format_RGB888
+ )
+ convert_to_Qt_format.scaled(
+ display_width, display_height, Qt.AspectRatioMode.KeepAspectRatio
+ )
+ return QPixmap.fromImage(convert_to_Qt_format)
+
+
+def convert_cv2scaledqt(cv_img, display_width, display_height):
+ """Convert from an opencv image to QPixmap"""
+ h, w, ch = cv_img.shape
+ bytes_per_line = ch * w
+ convert_to_Qt_format = QtGui.QImage(
+ cv_img.data, w, h, bytes_per_line, QtGui.QImage.Format.Format_RGB888
+ )
+ p = convert_to_Qt_format.scaled(
+ display_width, display_height, Qt.AspectRatioMode.KeepAspectRatio
+ )
+ return QPixmap.fromImage(p)