geospatial-ai-tool 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geospatial_ai_tool-0.1.0/.gitignore +88 -0
- geospatial_ai_tool-0.1.0/GeoAI_App.spec +61 -0
- geospatial_ai_tool-0.1.0/PKG-INFO +20 -0
- geospatial_ai_tool-0.1.0/README.md +1 -0
- geospatial_ai_tool-0.1.0/environment.yml +46 -0
- geospatial_ai_tool-0.1.0/pyproject.toml +40 -0
- geospatial_ai_tool-0.1.0/src/impr_app/__init__.py +0 -0
- geospatial_ai_tool-0.1.0/src/impr_app/core/app_shell.py +323 -0
- geospatial_ai_tool-0.1.0/src/impr_app/core/models.py +70 -0
- geospatial_ai_tool-0.1.0/src/impr_app/impr_app.py +48 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/base_page.py +51 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/home_page.py +282 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/output_page.py +621 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/page_2d.py +668 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/page_3d.py +224 -0
- geospatial_ai_tool-0.1.0/src/impr_app/pages/settings_page.py +89 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_2d/DOE_ANN_v2/doe_tiff/__init__.py +19 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_2d/DOE_ANN_v2/doe_tiff/doe_kernel.py +97 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_2d/DOE_ANN_v2/doe_tiff/io.py +68 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_2d/DOE_ANN_v2/predict3.py +169 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_2d/DOE_ANN_v2/stack_rasters_for_geoai.py +205 -0
- geospatial_ai_tool-0.1.0/src/impr_app/scripts_3d/threed.py +182 -0
- geospatial_ai_tool-0.1.0/src/impr_app/services/geoai_metrics.py +709 -0
- geospatial_ai_tool-0.1.0/src/impr_app/services/raster_processing.py +293 -0
- geospatial_ai_tool-0.1.0/src/impr_app/services/script_runners.py +449 -0
- geospatial_ai_tool-0.1.0/src/impr_app/ui_helpers.py +96 -0
- geospatial_ai_tool-0.1.0/tifviewer.py +41 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# =========================
|
|
2
|
+
# Environments
|
|
3
|
+
# =========================
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
env/
|
|
7
|
+
.env/
|
|
8
|
+
.conda/
|
|
9
|
+
miniconda3/
|
|
10
|
+
anaconda3/
|
|
11
|
+
|
|
12
|
+
# =========================
|
|
13
|
+
# Python Cache & Build
|
|
14
|
+
# =========================
|
|
15
|
+
__pycache__/
|
|
16
|
+
*.py[cod]
|
|
17
|
+
*$py.class
|
|
18
|
+
build/
|
|
19
|
+
develop-eggs/
|
|
20
|
+
dist/
|
|
21
|
+
downloads/
|
|
22
|
+
eggs/
|
|
23
|
+
.eggs/
|
|
24
|
+
parts/
|
|
25
|
+
sdist/
|
|
26
|
+
var/
|
|
27
|
+
wheels/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
.installed.cfg
|
|
30
|
+
*.egg
|
|
31
|
+
|
|
32
|
+
# =========================
|
|
33
|
+
# OS & Editor Files
|
|
34
|
+
# =========================
|
|
35
|
+
.DS_Store
|
|
36
|
+
.vscode/
|
|
37
|
+
.idea/
|
|
38
|
+
*.swp
|
|
39
|
+
|
|
40
|
+
# =========================
|
|
41
|
+
# Databases & Local State
|
|
42
|
+
# =========================
|
|
43
|
+
*.db
|
|
44
|
+
*.sqlite3
|
|
45
|
+
.jupyter/
|
|
46
|
+
.pytest_cache/
|
|
47
|
+
|
|
48
|
+
# =========================
|
|
49
|
+
# Data & Large Files
|
|
50
|
+
# =========================
|
|
51
|
+
# Ignore local raster/geospatial files and datasets
|
|
52
|
+
*.tif
|
|
53
|
+
*.tiff
|
|
54
|
+
*.grd
|
|
55
|
+
*.gri
|
|
56
|
+
*.hdr
|
|
57
|
+
*.nc
|
|
58
|
+
*.sdat
|
|
59
|
+
*.db
|
|
60
|
+
*.cpg
|
|
61
|
+
*.prj
|
|
62
|
+
*.dbf
|
|
63
|
+
*.sbn
|
|
64
|
+
*.shp
|
|
65
|
+
*.shx
|
|
66
|
+
*.sbx
|
|
67
|
+
*.xml
|
|
68
|
+
*.mgrd
|
|
69
|
+
*.sgrd
|
|
70
|
+
*.aux
|
|
71
|
+
*.kmz
|
|
72
|
+
data/
|
|
73
|
+
temp/
|
|
74
|
+
|
|
75
|
+
# =========================
|
|
76
|
+
# Secrets & Env Variables
|
|
77
|
+
# =========================
|
|
78
|
+
.env
|
|
79
|
+
|
|
80
|
+
# =========================
|
|
81
|
+
# Project Specifics
|
|
82
|
+
# =========================
|
|
83
|
+
GeoAI_Outputs/
|
|
84
|
+
saved_models.json
|
|
85
|
+
Pretrained_Brady_model/
|
|
86
|
+
3D_AI/
|
|
87
|
+
MYLES_2D_AI_MODELS_CLEAN/
|
|
88
|
+
training_stacks/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- mode: python ; coding: utf-8 -*-
|
|
2
|
+
from PyInstaller.utils.hooks import collect_all
|
|
3
|
+
|
|
4
|
+
datas = []
|
|
5
|
+
binaries = []
|
|
6
|
+
hiddenimports = []
|
|
7
|
+
tmp_ret = collect_all('customtkinter')
|
|
8
|
+
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
|
9
|
+
tmp_ret = collect_all('tensorflow')
|
|
10
|
+
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
|
11
|
+
tmp_ret = collect_all('rasterio')
|
|
12
|
+
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
a = Analysis(
|
|
16
|
+
['impr_app.py'],
|
|
17
|
+
pathex=[],
|
|
18
|
+
binaries=binaries,
|
|
19
|
+
datas=datas,
|
|
20
|
+
hiddenimports=hiddenimports,
|
|
21
|
+
hookspath=[],
|
|
22
|
+
hooksconfig={},
|
|
23
|
+
runtime_hooks=[],
|
|
24
|
+
excludes=[],
|
|
25
|
+
noarchive=False,
|
|
26
|
+
optimize=0,
|
|
27
|
+
)
|
|
28
|
+
pyz = PYZ(a.pure)
|
|
29
|
+
|
|
30
|
+
exe = EXE(
|
|
31
|
+
pyz,
|
|
32
|
+
a.scripts,
|
|
33
|
+
[],
|
|
34
|
+
exclude_binaries=True,
|
|
35
|
+
name='GeoAI_App',
|
|
36
|
+
debug=False,
|
|
37
|
+
bootloader_ignore_signals=False,
|
|
38
|
+
strip=False,
|
|
39
|
+
upx=True,
|
|
40
|
+
console=False,
|
|
41
|
+
disable_windowed_traceback=False,
|
|
42
|
+
argv_emulation=False,
|
|
43
|
+
target_arch=None,
|
|
44
|
+
codesign_identity=None,
|
|
45
|
+
entitlements_file=None,
|
|
46
|
+
)
|
|
47
|
+
coll = COLLECT(
|
|
48
|
+
exe,
|
|
49
|
+
a.binaries,
|
|
50
|
+
a.datas,
|
|
51
|
+
strip=False,
|
|
52
|
+
upx=True,
|
|
53
|
+
upx_exclude=[],
|
|
54
|
+
name='GeoAI_App',
|
|
55
|
+
)
|
|
56
|
+
app = BUNDLE(
|
|
57
|
+
coll,
|
|
58
|
+
name='GeoAI.app',
|
|
59
|
+
icon="MyIcon.icns",
|
|
60
|
+
bundle_identifier=None,
|
|
61
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: geospatial-ai-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 2D and 3D geospatial AI model prediction tool.
|
|
5
|
+
Requires-Python: ==3.10.*
|
|
6
|
+
Requires-Dist: customtkinter
|
|
7
|
+
Requires-Dist: geopandas
|
|
8
|
+
Requires-Dist: matplotlib
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: pandas
|
|
11
|
+
Requires-Dist: pillow
|
|
12
|
+
Requires-Dist: pyshortcuts
|
|
13
|
+
Requires-Dist: rasterio
|
|
14
|
+
Requires-Dist: scikit-learn
|
|
15
|
+
Requires-Dist: tensorflow==2.21.0
|
|
16
|
+
Requires-Dist: tkinterdnd2
|
|
17
|
+
Requires-Dist: tqdm
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
empty lol
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
empty lol
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: geo_app
|
|
2
|
+
channels:
|
|
3
|
+
- conda-forge
|
|
4
|
+
dependencies:
|
|
5
|
+
- python=3.10
|
|
6
|
+
- numpy
|
|
7
|
+
- tqdm
|
|
8
|
+
- pillow
|
|
9
|
+
- geopandas
|
|
10
|
+
- tk
|
|
11
|
+
- pandas
|
|
12
|
+
- matplotlib
|
|
13
|
+
- gdal
|
|
14
|
+
- rasterio
|
|
15
|
+
- scikit-learn
|
|
16
|
+
- pip:
|
|
17
|
+
- absl-py==2.5.0
|
|
18
|
+
- altgraph==0.17.5
|
|
19
|
+
- astunparse==1.6.3
|
|
20
|
+
- customtkinter==6.0.0
|
|
21
|
+
- darkdetect==0.8.0
|
|
22
|
+
- flatbuffers==25.12.19
|
|
23
|
+
- gast==0.7.0
|
|
24
|
+
- google-pasta==0.2.0
|
|
25
|
+
- grpcio==1.83.1
|
|
26
|
+
- h5py==3.14.0
|
|
27
|
+
- keras==3.12.4
|
|
28
|
+
- libclang==18.1.1
|
|
29
|
+
- macholib==1.16.4
|
|
30
|
+
- markdown-it-py==4.2.0
|
|
31
|
+
- mdurl==0.1.2
|
|
32
|
+
- ml-dtypes==0.6.0
|
|
33
|
+
- namex==0.1.0
|
|
34
|
+
- opt-einsum==3.4.0
|
|
35
|
+
- optree==0.20.0
|
|
36
|
+
- protobuf==7.36.1
|
|
37
|
+
- pygments==2.21.0
|
|
38
|
+
- pyinstaller==6.22.2
|
|
39
|
+
- pyinstaller-hooks-contrib==2026.7
|
|
40
|
+
- rich==15.0.0
|
|
41
|
+
- tensorflow==2.21.0
|
|
42
|
+
- termcolor==3.3.0
|
|
43
|
+
- tkinterdnd2==0.6.3
|
|
44
|
+
- typing-extensions==4.16.0
|
|
45
|
+
- wrapt==2.4.0
|
|
46
|
+
prefix: /opt/homebrew/Caskroom/miniforge/base/envs/my_app
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "geospatial-ai-tool" # The pip/uv install name (must be unique on PyPI)
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "2D and 3D geospatial AI model prediction tool."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = "==3.10.*"
|
|
11
|
+
dependencies = [
|
|
12
|
+
# Core Application
|
|
13
|
+
"customtkinter",
|
|
14
|
+
"pyshortcuts",
|
|
15
|
+
|
|
16
|
+
# Data & Machine Learning
|
|
17
|
+
"numpy",
|
|
18
|
+
"pandas",
|
|
19
|
+
"scikit-learn",
|
|
20
|
+
"tensorflow==2.21.0", # Pinned to the version you are currently using
|
|
21
|
+
|
|
22
|
+
# Geospatial
|
|
23
|
+
"geopandas",
|
|
24
|
+
"rasterio",
|
|
25
|
+
|
|
26
|
+
# Utilities
|
|
27
|
+
"matplotlib",
|
|
28
|
+
"pillow",
|
|
29
|
+
"tqdm",
|
|
30
|
+
|
|
31
|
+
"tkinterdnd2"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
# This creates the terminal command.
|
|
36
|
+
# Format: terminal-command = "module.file:function"
|
|
37
|
+
run-geospatial = "impr_app.impr_app:main"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/impr_app"]
|
|
File without changes
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# core/app_shell.py
|
|
2
|
+
import os
|
|
3
|
+
import queue
|
|
4
|
+
import shutil
|
|
5
|
+
import tkinter as tk
|
|
6
|
+
from tkinter import messagebox
|
|
7
|
+
|
|
8
|
+
import customtkinter as ctk
|
|
9
|
+
from impr_app.core.models import SavedModelLibrary
|
|
10
|
+
from impr_app.pages.home_page import HomePage
|
|
11
|
+
from impr_app.pages.output_page import OutputPage
|
|
12
|
+
from impr_app.pages.page_2d import Page2D
|
|
13
|
+
from impr_app.pages.page_3d import Page3D
|
|
14
|
+
from impr_app.pages.settings_page import SettingsPage
|
|
15
|
+
from tkinterdnd2 import DND_FILES, TkinterDnD
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DnDCTk(ctk.CTk, TkinterDnD.DnDWrapper):
|
|
19
|
+
"""Enables drag and drop feature for the UI as a whole"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
"""Initializes custom tkinter with drag and drop tkinter"""
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
self.TkdndVersion = TkinterDnD._require(self)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class App(DnDCTk):
|
|
28
|
+
"""Main ctk controller that manages generic settings like app width as
|
|
29
|
+
well as the generation of pages and switching between them.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
"""Builds window, initializes queue, activates drag and drop, builds pages
|
|
34
|
+
and shows the home page.
|
|
35
|
+
"""
|
|
36
|
+
self.THEMES = {
|
|
37
|
+
# ---------------------------------------------------------
|
|
38
|
+
# DEFAULT: Your current earth-toned & navy palette
|
|
39
|
+
# ---------------------------------------------------------
|
|
40
|
+
"Earth-Toned": {
|
|
41
|
+
"bg": "#EDF1F5",
|
|
42
|
+
"card_bg": "#FFFFFF",
|
|
43
|
+
"border": "#D5DBE1",
|
|
44
|
+
"text_primary": "#182A45",
|
|
45
|
+
"text_secondary": "#63758A",
|
|
46
|
+
"text_muted": "#4F6075",
|
|
47
|
+
"text_inverse": "white",
|
|
48
|
+
"text_input": "black",
|
|
49
|
+
"text_placeholder": "#8A9BB0",
|
|
50
|
+
"badge_bg": "#8F7870",
|
|
51
|
+
"input_bg": "#F8FAFC",
|
|
52
|
+
"btn_primary": "#D4B592",
|
|
53
|
+
"btn_primary_hover": "#C0A07B",
|
|
54
|
+
"btn_secondary_bg": "#FFFFFF",
|
|
55
|
+
"btn_secondary_hover": "#F0F4F8",
|
|
56
|
+
"btn_accent": "#F2C94C",
|
|
57
|
+
"btn_accent_hover": "#D4AF37",
|
|
58
|
+
"btn_dropdown_hover": "#7A654E",
|
|
59
|
+
},
|
|
60
|
+
# ---------------------------------------------------------
|
|
61
|
+
# DEUTERANOPIA (Red/Green weak - Most Common)
|
|
62
|
+
# Replaces warm earth tones with distinct cool blues and bright oranges.
|
|
63
|
+
# ---------------------------------------------------------
|
|
64
|
+
"Default (deuteranopia)": {
|
|
65
|
+
"bg": "#EDF1F5",
|
|
66
|
+
"card_bg": "#FFFFFF",
|
|
67
|
+
"border": "#D5DBE1",
|
|
68
|
+
"text_primary": "#182A45",
|
|
69
|
+
"text_secondary": "#63758A",
|
|
70
|
+
"text_muted": "#4F6075",
|
|
71
|
+
"text_inverse": "white",
|
|
72
|
+
"text_input": "black",
|
|
73
|
+
"text_placeholder": "#8A9BB0",
|
|
74
|
+
"badge_bg": "#0072B2", # Replaced warm brown with Dark Blue
|
|
75
|
+
"input_bg": "#F8FAFC",
|
|
76
|
+
"btn_primary": "#56B4E9", # Replaced Tan with Sky Blue
|
|
77
|
+
"btn_primary_hover": "#3D8CBB",
|
|
78
|
+
"btn_secondary_bg": "#FFFFFF",
|
|
79
|
+
"btn_secondary_hover": "#F0F4F8",
|
|
80
|
+
"btn_accent": "#E69F00", # Replaced Gold with High-Vis Orange
|
|
81
|
+
"btn_accent_hover": "#C28600",
|
|
82
|
+
"btn_dropdown_hover": "#005A8E",
|
|
83
|
+
},
|
|
84
|
+
# ---------------------------------------------------------
|
|
85
|
+
# PROTANOPIA (Red-blind)
|
|
86
|
+
# Similar to Deuteranopia, but avoids dark reds/browns which appear black.
|
|
87
|
+
# ---------------------------------------------------------
|
|
88
|
+
"protanopia": {
|
|
89
|
+
"bg": "#EDF1F5",
|
|
90
|
+
"card_bg": "#FFFFFF",
|
|
91
|
+
"border": "#D5DBE1",
|
|
92
|
+
"text_primary": "#182A45",
|
|
93
|
+
"text_secondary": "#63758A",
|
|
94
|
+
"text_muted": "#4F6075",
|
|
95
|
+
"text_inverse": "white",
|
|
96
|
+
"text_input": "black",
|
|
97
|
+
"text_placeholder": "#8A9BB0",
|
|
98
|
+
"badge_bg": "#E69F00", # Replaced warm brown with Bright Orange
|
|
99
|
+
"input_bg": "#F8FAFC",
|
|
100
|
+
"btn_primary": "#0072B2", # Replaced Tan with deep, visible Blue
|
|
101
|
+
"btn_primary_hover": "#005A8E",
|
|
102
|
+
"btn_secondary_bg": "#FFFFFF",
|
|
103
|
+
"btn_secondary_hover": "#F0F4F8",
|
|
104
|
+
"btn_accent": "#F0E442", # Replaced Gold with brighter Yellow
|
|
105
|
+
"btn_accent_hover": "#D9CE36",
|
|
106
|
+
"btn_dropdown_hover": "#00456E",
|
|
107
|
+
},
|
|
108
|
+
# ---------------------------------------------------------
|
|
109
|
+
# TRITANOPIA (Blue/Yellow blind - Rare)
|
|
110
|
+
# Strips out the Navy Blue and Gold completely.
|
|
111
|
+
# Uses Charcoal for text, Vermilion (Red) and Teal for actions.
|
|
112
|
+
# ---------------------------------------------------------
|
|
113
|
+
"tritanopia": {
|
|
114
|
+
"bg": "#F4F4F4", # Shifted away from cool blue-grey
|
|
115
|
+
"card_bg": "#FFFFFF",
|
|
116
|
+
"border": "#D0D0D0",
|
|
117
|
+
"text_primary": "#2C2C2C", # Shifted Navy to neutral Charcoal
|
|
118
|
+
"text_secondary": "#5A5A5A", # Shifted Slate to neutral Grey
|
|
119
|
+
"text_muted": "#4A4A4A",
|
|
120
|
+
"text_inverse": "white",
|
|
121
|
+
"text_input": "black",
|
|
122
|
+
"text_placeholder": "#999999",
|
|
123
|
+
"badge_bg": "#CC79A7", # Reddish-purple (very clear for Tritanopes)
|
|
124
|
+
"input_bg": "#FAFAFA",
|
|
125
|
+
"btn_primary": "#D55E00", # Vermilion (Red-Orange) replaces Tan
|
|
126
|
+
"btn_primary_hover": "#A34800",
|
|
127
|
+
"btn_secondary_bg": "#FFFFFF",
|
|
128
|
+
"btn_secondary_hover": "#EEEEEE",
|
|
129
|
+
"btn_accent": "#009E73", # Teal/Green replaces Gold
|
|
130
|
+
"btn_accent_hover": "#007A59",
|
|
131
|
+
"btn_dropdown_hover": "#7A3500",
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
super().__init__()
|
|
136
|
+
self.theme_colors = self.THEMES["Default (deuteranopia)"]
|
|
137
|
+
self.configure(fg_color=self.theme_colors["bg"])
|
|
138
|
+
self.current_scale = 1.2
|
|
139
|
+
screen_width = self.winfo_screenwidth()
|
|
140
|
+
screen_height = self.winfo_screenheight()
|
|
141
|
+
# UI Looks better when using the entire screen, also gives images
|
|
142
|
+
# more space
|
|
143
|
+
self.geometry(f"{screen_width}x{screen_height}")
|
|
144
|
+
self.title("GEO AI for Geothermal Exploration")
|
|
145
|
+
|
|
146
|
+
# Multithreading is used so the UI does not freeze during processing,
|
|
147
|
+
# which makes the queue necessary for thread safety
|
|
148
|
+
self.log_queue = queue.Queue()
|
|
149
|
+
self.model_library = SavedModelLibrary("DEADMEAT")
|
|
150
|
+
self.current_probability_map = None
|
|
151
|
+
|
|
152
|
+
self.container = ctk.CTkFrame(self)
|
|
153
|
+
self.container = ctk.CTkFrame(self, fg_color="transparent")
|
|
154
|
+
self.container.pack(fill="both", expand=True)
|
|
155
|
+
|
|
156
|
+
self.pages = {}
|
|
157
|
+
self.active_page = None
|
|
158
|
+
|
|
159
|
+
self._build_pages()
|
|
160
|
+
|
|
161
|
+
self.drop_target_register(DND_FILES)
|
|
162
|
+
self.dnd_bind("<<Drop>>", self._handle_drop)
|
|
163
|
+
|
|
164
|
+
self.show_page("HomePage")
|
|
165
|
+
self.apply_scaling(self.current_scale)
|
|
166
|
+
self.after(100, self.check_queue)
|
|
167
|
+
|
|
168
|
+
self.saved_files = []
|
|
169
|
+
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
|
170
|
+
|
|
171
|
+
def _build_pages(self):
|
|
172
|
+
"""Loops through every provided page class, creates an object
|
|
173
|
+
for each page, and saves those objects to the pages dict.
|
|
174
|
+
"""
|
|
175
|
+
for PageClass in (HomePage, Page2D, Page3D, OutputPage, SettingsPage):
|
|
176
|
+
page = PageClass(self.container, controller=self)
|
|
177
|
+
self.pages[PageClass.__name__] = page
|
|
178
|
+
|
|
179
|
+
def show_page(self, page_name):
|
|
180
|
+
"""Shows the desired page while hiding the others.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
page_name (str): key for dictionary containing corresponding
|
|
184
|
+
homepage object.
|
|
185
|
+
"""
|
|
186
|
+
# Unpacking pages instead of hiding them for improved efficiency
|
|
187
|
+
for page in self.pages.values():
|
|
188
|
+
page.pack_forget()
|
|
189
|
+
page = self.pages[page_name]
|
|
190
|
+
page.pack(fill="both", expand=True)
|
|
191
|
+
self.active_page = page
|
|
192
|
+
page.on_show()
|
|
193
|
+
self.update_idletasks()
|
|
194
|
+
|
|
195
|
+
def check_queue(self):
|
|
196
|
+
"""Loops through queue to display messages to app console safely.
|
|
197
|
+
Continuously calls itself.
|
|
198
|
+
"""
|
|
199
|
+
while not self.log_queue.empty():
|
|
200
|
+
message = self.log_queue.get()
|
|
201
|
+
# This prevents another thread other than the UI thread from
|
|
202
|
+
# trying to manipulate the log box
|
|
203
|
+
self.active_page.append_log(message)
|
|
204
|
+
self.after(100, self.check_queue)
|
|
205
|
+
|
|
206
|
+
def _handle_drop(self, event):
|
|
207
|
+
"""Checks if a page uses drag and drop, if it does
|
|
208
|
+
has page handle the drop
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
event (tkinter.Event): Contains data about the drag and drop action
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# Not all pages in the application use drag and drop
|
|
215
|
+
if hasattr(self.active_page, "handle_drop"):
|
|
216
|
+
self.active_page.handle_drop(event)
|
|
217
|
+
|
|
218
|
+
def change_theme(self, new_theme_name):
|
|
219
|
+
# Store the old colors before we update so we can map them
|
|
220
|
+
old_colors = self.theme_colors
|
|
221
|
+
|
|
222
|
+
# Update to the new colors
|
|
223
|
+
self.current_theme_name = new_theme_name
|
|
224
|
+
self.theme_colors = self.THEMES[new_theme_name]
|
|
225
|
+
|
|
226
|
+
for page in self.pages.values():
|
|
227
|
+
page.colors = self.theme_colors
|
|
228
|
+
|
|
229
|
+
# Update the main window background
|
|
230
|
+
self.configure(fg_color=self.theme_colors["bg"])
|
|
231
|
+
|
|
232
|
+
# Start the recursion, passing both old and new dictionaries
|
|
233
|
+
self._update_widget_tree(self, old_colors, self.theme_colors)
|
|
234
|
+
|
|
235
|
+
def _update_widget_tree(self, parent_widget, old_colors, new_colors):
|
|
236
|
+
"""Recursively checks and updates every widget."""
|
|
237
|
+
for widget in parent_widget.winfo_children():
|
|
238
|
+
# Update the 4 color properties CTk uses in your UI
|
|
239
|
+
self._swap_color(widget, "fg_color", old_colors, new_colors)
|
|
240
|
+
self._swap_color(widget, "text_color", old_colors, new_colors)
|
|
241
|
+
self._swap_color(widget, "border_color", old_colors, new_colors)
|
|
242
|
+
self._swap_color(widget, "hover_color", old_colors, new_colors)
|
|
243
|
+
self._swap_color(widget, "button_color", old_colors, new_colors)
|
|
244
|
+
self._swap_color(widget, "button_hover_color", old_colors, new_colors)
|
|
245
|
+
|
|
246
|
+
# Recurse deeper if this widget has children
|
|
247
|
+
if widget.winfo_children():
|
|
248
|
+
self._update_widget_tree(widget, old_colors, new_colors)
|
|
249
|
+
|
|
250
|
+
def _swap_color(self, widget, property_name, old_colors, new_colors):
|
|
251
|
+
"""Looks at a widget's current hex code and maps it to the new theme."""
|
|
252
|
+
try:
|
|
253
|
+
# cget() grabs the current value of a widget property
|
|
254
|
+
current_color = widget.cget(property_name)
|
|
255
|
+
|
|
256
|
+
# CTk sometimes returns colors as a list/tuple for light/dark mode (e.g. ['#FFF', '#000'])
|
|
257
|
+
# We just need to check the string representation
|
|
258
|
+
if isinstance(current_color, (list, tuple)):
|
|
259
|
+
current_color = current_color[0]
|
|
260
|
+
|
|
261
|
+
# Never alter transparent widgets
|
|
262
|
+
if str(current_color).lower() == "transparent":
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
# Find this color in the OLD dictionary, apply from the NEW dictionary
|
|
266
|
+
for key, old_hex in old_colors.items():
|
|
267
|
+
if str(current_color).lower() == str(old_hex).lower():
|
|
268
|
+
# We found a match! Apply the new hex code for this key.
|
|
269
|
+
# **{} is just dynamically unpacking the property name (e.g. fg_color="#NEW_HEX")
|
|
270
|
+
widget.configure(**{property_name: new_colors[key]})
|
|
271
|
+
return # Stop searching once we find a match
|
|
272
|
+
|
|
273
|
+
except (ValueError, tk.TclError, AttributeError):
|
|
274
|
+
# Pass gracefully if the widget doesn't support the property we are checking
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
def update_scaling(self, new_scale):
|
|
278
|
+
self.current_scale = new_scale
|
|
279
|
+
|
|
280
|
+
self.apply_scaling(new_scale)
|
|
281
|
+
|
|
282
|
+
def apply_scaling(self, scale_factor):
|
|
283
|
+
ctk.set_widget_scaling(scale_factor)
|
|
284
|
+
|
|
285
|
+
# Get the current dimensions of the window
|
|
286
|
+
current_width = self.winfo_width()
|
|
287
|
+
current_height = self.winfo_height()
|
|
288
|
+
|
|
289
|
+
# Expand the window by 1 pixel to force a hard OS-level redraw
|
|
290
|
+
self.geometry(f"{current_width + 1}x{current_height}")
|
|
291
|
+
self.update()
|
|
292
|
+
|
|
293
|
+
# Snap it instantly back to the correct size
|
|
294
|
+
self.geometry(f"{current_width}x{current_height}")
|
|
295
|
+
|
|
296
|
+
def on_closing(self):
|
|
297
|
+
# Trigger the pop-up when the window close is attempted
|
|
298
|
+
# askyesnocancel returns: True (Yes), False (No), or None (Cancel)
|
|
299
|
+
response = messagebox.askyesnocancel(
|
|
300
|
+
title="Save before closing?",
|
|
301
|
+
message="Would you like to save your generated files before exiting?",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Handle the user's choice
|
|
305
|
+
if response:
|
|
306
|
+
self.destroy()
|
|
307
|
+
|
|
308
|
+
elif response is False:
|
|
309
|
+
for dir_path in self.saved_files:
|
|
310
|
+
if os.path.exists(dir_path):
|
|
311
|
+
shutil.rmtree(dir_path)
|
|
312
|
+
self.destroy()
|
|
313
|
+
|
|
314
|
+
def register_saved_file(self, filepath):
|
|
315
|
+
"""Safely appends a newly generated file path to the saved files list."""
|
|
316
|
+
if filepath not in self.saved_files:
|
|
317
|
+
self.saved_files.append(filepath)
|
|
318
|
+
print(f"Tracking new file: {filepath}") # Optional: for debugging
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
app = App()
|
|
323
|
+
app.mainloop()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# core/models.py
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SavedModelLibrary:
|
|
7
|
+
"""Manages the saving, loading, and deletion of user-imported AI models
|
|
8
|
+
to a local JSON file so they persist between app sessions.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, memory_file_path: str) -> None:
|
|
12
|
+
"""Initializes saved model library with memory file path
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
memory_file_path (str): raw relative file path to the applications saved memory.
|
|
16
|
+
"""
|
|
17
|
+
memory_file_path = Path.home() / "Documents"
|
|
18
|
+
print(memory_file_path)
|
|
19
|
+
|
|
20
|
+
def set_memory_file(self, memory_file_path: str) -> None:
|
|
21
|
+
self.memory_file = Path(memory_file_path) / "saved_models.json"
|
|
22
|
+
if not self.memory_file.exists():
|
|
23
|
+
self.memory_file.write_text("{}")
|
|
24
|
+
|
|
25
|
+
def get_all_models(self) -> dict:
|
|
26
|
+
"""Loads and returns the dictionary of saved models.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
dict: A dictionary containing model names as keys which point to their file paths or
|
|
30
|
+
an empty dictionary if file DNE or is corrupted
|
|
31
|
+
"""
|
|
32
|
+
if self.memory_file.exists():
|
|
33
|
+
try:
|
|
34
|
+
with open(self.memory_file, "r") as f:
|
|
35
|
+
return json.load(f)
|
|
36
|
+
except json.JSONDecodeError:
|
|
37
|
+
return {}
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
def _save_models(self, models_dict: dict) -> None:
|
|
41
|
+
"""Helper function that saves the models in dictionary to json file
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
models_dict (dict): A dictionary containing user generated label
|
|
45
|
+
and filepath to model.
|
|
46
|
+
"""
|
|
47
|
+
with open(self.memory_file, "w") as f:
|
|
48
|
+
json.dump(models_dict, f, indent=4)
|
|
49
|
+
|
|
50
|
+
def add_model(self, name: str, filepath: str | Path) -> None:
|
|
51
|
+
"""Adds a GeoAI model to the UI as a selectable choice
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name (str): The name a user has given the model
|
|
55
|
+
filepath (str | Path): The filepath to the model they selected
|
|
56
|
+
"""
|
|
57
|
+
models = self.get_all_models()
|
|
58
|
+
models[name] = str(filepath)
|
|
59
|
+
self._save_models(models)
|
|
60
|
+
|
|
61
|
+
def delete_model(self, name) -> None:
|
|
62
|
+
"""Deletes model from working dictionary (if exists) and updates the json model data
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
name (str): Name the user generated for a specific model
|
|
66
|
+
"""
|
|
67
|
+
models = self.get_all_models()
|
|
68
|
+
if name in models:
|
|
69
|
+
del models[name]
|
|
70
|
+
self._save_models(models)
|