Skip to content
Closed

Merge #413

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
23f3d8e
vector-service
shevi-kukis Nov 23, 2025
1892512
adding irrigation view
t5334 Nov 23, 2025
61e7abb
vector-service-sensors
shevi-kukis Nov 23, 2025
6326b6b
Merge branch 'main' into tamar/gui_irrigation
t5334 Nov 23, 2025
8f3006b
Merge pull request #409 from KamaTechOrg/tamar/gui_irrigation
t5334 Nov 23, 2025
00d8e18
Update app.py
shiffiH Nov 24, 2025
2168d7d
Merge pull request #411 from KamaTechOrg/Shiffi/Whiter_db
shiffiH Nov 24, 2025
ae1d61d
Remove helper methods for clause management
Yehudit10 Nov 25, 2025
2852b2f
Simplify SQL expression checks in fragment method
Yehudit10 Nov 25, 2025
62055e2
Remove commented PostgresDialect implementation
Yehudit10 Nov 25, 2025
cdb7ba3
Add Func class for function expressions
Yehudit10 Nov 25, 2025
af6567e
Refactor SelectOp and WhereOp to use add_clause
Yehudit10 Nov 25, 2025
9193b2c
Update ops.py
Yehudit10 Nov 25, 2025
684d9ef
Enhance tile loading and background image support
Yehudit10 Nov 25, 2025
ef2d386
Add files via upload
Yehudit10 Nov 25, 2025
9032ae0
Enhance error handling in get_final_mp4 function
Yehudit10 Nov 25, 2025
2e07e28
Replace _DeviceMarker with _Camera360Marker
Yehudit10 Nov 25, 2025
e67ca81
Refactor RegionLayer for better polygon handling
Yehudit10 Nov 25, 2025
1be6d48
Implement AgGuardMessageBox and enhance UI
Yehudit10 Nov 25, 2025
55946bd
Refactor DSL generator prompt and guidelines
Yehudit10 Nov 25, 2025
54123b6
Refactor data publisher and adjust publish interval
Yehudit10 Nov 25, 2025
e14b551
Enhance HlsRecorder with delete delay and logging
Yehudit10 Nov 25, 2025
29385b9
Refactor aggregator.py for readability and threading
Yehudit10 Nov 25, 2025
981e430
Adjust video player dialog size and clean up code
Yehudit10 Nov 25, 2025
9d2f978
Update incident_player_vlc.py
Yehudit10 Nov 25, 2025
b97483b
Merge pull request #412 from KamaTechOrg/yehudit/full_flow2
Yehudit10 Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion GUI/src/vast/desktop/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ RUN useradd -m -s /bin/bash appuser && \
mkdir -p /app /tmp/.X11-unix && chown -R appuser:appuser /app /tmp /opt/noVNC /var/tmp

RUN apt-get update && apt-get install -y --no-install-recommends gosu && rm -rf /var/lib/apt/lists/*

RUN pip install psycopg2-binary
COPY src/vast /app/src/vast
COPY src/vast/desktop/start.sh /app/start.sh
RUN sed -i 's/\r$//' /app/start.sh && chmod +x /app/start.sh && chown -R appuser:appuser /app
Expand Down
8 changes: 2 additions & 6 deletions GUI/src/vast/dsl/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ class SQLState:
"select", "from", "where", "group_by", "having", "order_by", "limit", "offset"
])

# Helper methods to avoid importing Clause classes in ops
def add_select(self, columns: List[str]) -> None:
self.clauses["select"].append(SelectClause(columns))
def add_where(self, cond) -> None:
self.clauses["where"].append(WhereClause(cond))

def add_clause(self, clause: Clause) -> None:
self.clauses[clause.phase].append(clause)

Expand Down Expand Up @@ -92,4 +88,4 @@ def compile(self, plan: Plan | Dict[str, Any]) -> tuple[str, List[Any]]:
# Pass all keys except "op" as kwargs
Op.registry[op_type](**{k: v for k, v in op.items() if k != "op"}).apply(st)

return st.build()
return st.build()
6 changes: 2 additions & 4 deletions GUI/src/vast/dsl/clauses.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,8 @@ def joiner(self): return ", "
def fragment(self, ctx):
def quote_or_passthrough(col: str) -> str:
# Skip quoting if it's clearly an SQL expression or aggregate
if any(token in col.upper() for token in (
"(", ")", " AS ", "COUNT", "AVG", "SUM", "MAX", "MIN"
)):
return col # leave expressions as-is
if "(" in col or ")" in col:
return col
return ctx.dialect.quote_ident(col)

cols = [quote_or_passthrough(c) for c in self.columns]
Expand Down
18 changes: 0 additions & 18 deletions GUI/src/vast/dsl/dialects.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,6 @@ def normalize_bool(self, v: Any) -> Any:
def placeholder(self, idx: int) -> str:
return "?" # qmark style

# class PostgresDialect(Dialect):
# def __init__(self, style: str = "psycopg"):
# """style:
# - 'psycopg' → %s style placeholders (psycopg2/3)
# - 'numeric' → $1, $2, ... style placeholders (asyncpg)
# """

# if style not in ("psycopg", "numeric"):
# raise ValueError("PostgresDialect.style must be 'psycopg' or 'numeric'")
# self.style = style
# def quote_ident(self, name: str) -> str:
# parts = name.split(".")
# return ".".join('"' + p.replace('"', '""') + '"' for p in parts)
# def normalize_bool(self, v: Any) -> Any:
# return v # PostgreSQL has a real boolean type
# def placeholder(self, idx: int) -> str:
# return "%s" if self.style == "psycopg" else f"${idx}"
# dialects.py
class PostgresDialect(Dialect):
def __init__(self, style: str = "named"):
"""
Expand Down
43 changes: 36 additions & 7 deletions GUI/src/vast/dsl/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ def compile(self, ctx: CompileCtx) -> str:
def to_ir(self) -> Dict[str, Any]:
return {"literal": self.value}

@dataclass
class Func(Expr):
name: str
args: list[Expr]

def compile(self, ctx: CompileCtx) -> str:
compiled = ", ".join(arg.compile(ctx) for arg in self.args)
return f"{self.name.upper()}({compiled})"

def to_ir(self) -> Dict[str, Any]:
return {
"func": self.name,
"args": [arg.to_ir() for arg in self.args]
}



def ensure_expr(x: Any) -> Expr:
"""Coerce Python values to Literal, leave Expr as-is."""
Expand All @@ -85,7 +101,7 @@ class Predicate(Cond):
op: BinOp
right: Expr
def __post_init__(self):
if not isinstance(self.left, (Col, Literal)) or not isinstance(self.right, (Col, Literal)):
if not isinstance(self.left, (Col, Literal,Func)) or not isinstance(self.right, (Col, Literal,Func)):
raise TypeError("Predicate must compare columns and/or literals only")
def compile(self, ctx: CompileCtx) -> str:
return f"({self.left.compile(ctx)} {self.op.value} {self.right.compile(ctx)})"
Expand All @@ -109,12 +125,25 @@ def to_ir(self) -> Dict[str, Any]: return {"any": [p.to_ir() for p in self.parts
# ---- Strict IR decoding ----

def expr_from_ir(d: Dict[str, Any]) -> Expr:
"""Decode a strict Expr IR object into Expr."""
if not isinstance(d, dict): raise TypeError("Expr leaf must be an object")
if not isinstance(d, dict):
raise TypeError("Expr leaf must be an object")

keys = set(d.keys())
if keys == {"col"}: return Col(d["col"])
if keys == {"literal"}: return Literal(d["literal"])
raise ValueError("Expr leaf must be either {\"col\": name} or {\"literal\": value}")

if keys == {"col"}:
return Col(d["col"])

if keys == {"literal"}:
return Literal(d["literal"])

if keys == {"func", "args"}:
return Func(
d["func"],
[expr_from_ir(arg) for arg in d["args"]]
)

raise ValueError(f"Invalid Expr IR: {d}")



def cond_from_ir(d: Dict[str, Any]) -> Cond:
Expand Down Expand Up @@ -168,4 +197,4 @@ def cond_from_ir(d: Dict[str, Any]) -> Cond:

# Convenience aliases
AND = All
OR = Any
OR = Any
13 changes: 7 additions & 6 deletions GUI/src/vast/dsl/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@ def apply(self, st: "SQLState") -> None:
class SelectOp(Op):
op_type = "select"
def apply(self, st: "SQLState") -> None:
cols = self.payload.get("columns")
st.add_select(cols or [])
cols = self.payload.get("columns", [])
st.add_clause(SelectClause(cols))


class WhereOp(Op):
op_type = "where"
def apply(self, st: "SQLState") -> None:
cond_ir = self.payload.get("cond")
if not isinstance(cond_ir, dict):
raise TypeError(
f"Invalid WHERE condition: expected dict, got {type(cond_ir).__name__} → {cond_ir}"
)
st.add_where(cond_from_ir(cond_ir))
raise TypeError("Invalid WHERE condition")
expr = cond_from_ir(cond_ir)
st.add_clause(WhereClause(expr))



class HavingOp(Op):
Expand Down
30 changes: 29 additions & 1 deletion GUI/src/vast/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,9 @@



# === Irrigation imports ===
from views.irrigation.irrigation_view import IrrigationView


class MainWindow(QMainWindow):
logoutRequested = pyqtSignal()
Expand Down Expand Up @@ -1138,7 +1141,7 @@ def reposition_badge():

for name in [
"Home", "Sensors", "Sound", "Ground Image",
"Aerial Image", "Fruits", "Security", "Settings", "Notifications"
"Aerial Image", "Fruits", "Security", "Settings", "Notifications", "Irrigation"
]:
QListWidgetItem(f" {name}", self.nav_list)

Expand Down Expand Up @@ -1175,6 +1178,21 @@ def reposition_badge():
self.notification_view = NotificationView(self)
self.security_view = IncidentPlayerVLC(api, self.alert_service, self)
self.fruits_view = FruitsView(api, self)
self.ground_view = GroundView(api, self)
self.auth_status = AuthStatusView(api, self)

self.sensors_status_summary = SensorsStatusSummary(api, self)
self.sensors_health = SensorsView(api, self)
self.sensors_main = SensorsMainView(api, self)
print("[DEBUG] Creating IrrigationView...")
try:
self.irrigation_view = IrrigationView(api, self)
print("[DEBUG] IrrigationView created successfully")
except Exception as e:
print(f"[ERROR] Failed to create IrrigationView: {e}")
import traceback
traceback.print_exc()
self.irrigation_view = QWidget() # Fallback empty widget

self.stack = QStackedWidget()
self.setCentralWidget(self.stack)
Expand All @@ -1185,6 +1203,9 @@ def reposition_badge():
"Notifications": self.notification_view,
"Security": self.security_view,
"Fruits": self.fruits_view,
"Ground Image": self.ground_view,
"Irrigation": self.irrigation_view,
"Auth": self.auth_status

}
for view in self.views.values():
Expand Down Expand Up @@ -1242,16 +1263,23 @@ def toggle_alert_panel(self):
# ───────────────────────────────
def _on_nav_change(self, row: int) -> None:
name = self.nav_list.item(row).text().strip()
print(f"[DEBUG] _on_nav_change: row={row}, name='{name}'")
print(f"[DEBUG] Available views: {list(self.views.keys())}")
if name in self.views:
print(f"[DEBUG] Navigating to '{name}'")
self.navigate_to(self.views[name])
else:
print(f"[DEBUG] Section '{name}' not found in views")
self.statusBar().showMessage(f"Section '{name}' not implemented yet.")

def navigate_to(self, widget):
print(f"[DEBUG] navigate_to called with widget: {widget.__class__.__name__}")
current = self.stack.currentWidget()
if current not in self.history:
self.history.append(current)
print(f"[DEBUG] Setting current widget to: {widget.__class__.__name__}")
self.stack.setCurrentWidget(widget)
print(f"[DEBUG] Current widget is now: {self.stack.currentWidget().__class__.__name__}")

def go_back(self):
if self.history:
Expand Down
Binary file added GUI/src/vast/orthophoto_canvas/ui/fields.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 56 additions & 14 deletions GUI/src/vast/orthophoto_canvas/ui/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from PyQt6.QtWidgets import (
QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsRectItem
)

from PyQt6.QtGui import QPixmap, QTransform
# ==== Tunables ====
TILE_SIZE = 512
TARGET_TILE_PX_FOR_LOD = 512.0
Expand All @@ -25,18 +25,7 @@ class OrthophotoViewer(QGraphicsView):
def __init__(self, tiles: Union[TileStore, str, Path]) -> None:
super().__init__()

# ─────────────────────────────
# Load tiles
# ─────────────────────────────
# if isinstance(tiles, TileStore):
# self.ts = tiles
# else:
# self.ts = TileStore(Path(tiles))

# self.min_zoom_fs = self.ts.min_zoom
# self.max_zoom_fs = self.ts.max_zoom
# self.z_ranges = self.ts.z_ranges
# self.is_tms = self.ts.is_tms

# ─────────────────────────────
# Load tiles
# ─────────────────────────────
Expand Down Expand Up @@ -125,8 +114,58 @@ def __init__(self, tiles: Union[TileStore, str, Path]) -> None:
# ─────────────────────────────
# Initial tile rendering
# ─────────────────────────────
self._custom_bg_item: Optional[QGraphicsPixmapItem] = None
self._tiles_visible: bool = True
self.update_tiles()


def _apply_tile_visibility(self):
"""Apply visibility/opacity preference to existing tile items."""
for item in self.tile_items.values():
# You can choose to hide or fade; here we just hide/show them.
item.setVisible(self._tiles_visible)
# If you prefer fading:
# item.setOpacity(0.2 if not self._tiles_visible else 1.0)



def set_custom_background_image(self, path: str, hide_tiles: bool = False):
"""
Place a single static image as the map background, scaled to the scene extents.
It will zoom & pan together with all other items.
"""
pix = QPixmap(path)
p = Path(path)
print("[OrthophotoViewer] Exists?", p.exists())
if pix.isNull():
print(f"[OrthophotoViewer] ❌ Failed to load background image: {path}")
return

# Remove previous bg if exists
if self._custom_bg_item is not None:
self.scene.removeItem(self._custom_bg_item)
self._custom_bg_item = None

scene_rect = self.scene.sceneRect()
width = scene_rect.width()
height = scene_rect.height()

item = QGraphicsPixmapItem(pix)
item.setZValue(-1000) # behind tiles, regions, sensors

# Scale to fill the entire scene rect
sx = width / pix.width() if pix.width() > 0 else 1.0
sy = height / pix.height() if pix.height() > 0 else 1.0
item.setTransform(QTransform().scale(sx, sy))

# Position at the scene rect origin (you use a small margin, so respect that)
item.setPos(scene_rect.left(), scene_rect.top())

self.scene.addItem(item)
self._custom_bg_item = item

if hide_tiles:
self._tiles_visible = False
self._apply_tile_visibility()

# ─────────────────────────────
# Scene geometry
Expand Down Expand Up @@ -228,6 +267,9 @@ def update_tiles(self) -> None:
if key not in want:
self.scene.removeItem(self.tile_items.pop(key))

# 🔹 Ensure visibility style is applied to all tiles (including new ones)
self._apply_tile_visibility()

# ─────────────────────────────
# Tile placement / upgrade
# ─────────────────────────────
Expand Down
Loading
Loading