0

I’m embedding a small UI with pywebview and want Python to JS live updates. I created a GPSSpoofingDetector class that loads a pickled sklearn model and a pandas test CSV. I want a JavaScript “Start” button to call a Python method that runs random_sample_testing, and during that test the Python code calls window.evaluate_js(...) to push JSON updates to the page.

This works if I run the test as the webview.start() callback (i.e. webview.start(detector.random_sample_testing, args=(window, data, feature_names), http_server=True)), but when I expose a Python API object to JS using js_api=api in webview.create_window() I immediately get errors before any button is clicked.

GPStest.py:

import time
import json
import webview

import pickle
import pandas as pd
import numpy as np
import warnings
from pathlib import Path

warnings.filterwarnings('ignore')

class GPSSpoofingDetector:
    """Wrap original script into a class with small functions while preserving
    exact prints, returns and behavior.

    Usage: run this file directly. It will perform the same prints and return
    values as the original script.
    """

    def __init__(self):
        # constants kept exactly as in the original script
        self.PROJECT_ROOT = Path(__file__).resolve().parents[2]  # -> GPS_Spoofing_Detection
        self.MODEL_PATH = self.PROJECT_ROOT / "model" / "DT_model.pkl"
        self.TEST_CSV = self.PROJECT_ROOT / "dataset" / "testing" / "test_data.csv"
        self.FEATURE_COLUMNS = [2, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26]
        self.clf = None
        self.scaler = None
        print('initializing')

    def load_model(self):
        """Load the trained model and scaler from pickle. Prints same messages."""
        print("Loading model...")
        with open(self.MODEL_PATH, 'rb') as f:
            self.clf, self.scaler = pickle.load(f)
        print("✓ Model loaded successfully\n")

    def predict_spoofing(self, gps_features_df, show_details=True):
        """
        Predict GPS spoofing from features

        Parameters:
        - gps_features_df: DataFrame with proper column names
        - show_details: show prediction probabilities

        Returns: prediction label and numeric class
        """
        features_scaled = self.scaler.transform(gps_features_df)
        prediction = self.clf.predict(features_scaled)

        labels = {0: 'Clean', 1: 'Static Spoofing', 2: 'Dynamic Spoofing'}
        result = labels[prediction[0]]

        if show_details and hasattr(self.clf, 'predict_proba'):
            probas = self.clf.predict_proba(features_scaled)[0]
            print(f"Prediction: {result}")
            print(f"Confidence:")
            for i, label in labels.items():
                print(f"  {label}: {probas[i]*100:.2f}%")

        return result, prediction[0]

    def load_data(self):
        """Load test CSV and return DataFrame. Keeps same variable names."""
        data = pd.read_csv(self.TEST_CSV)
        return data

    def print_features_used(self, data):
        """Print feature names used exactly as original script."""
        feature_names = data.columns[self.FEATURE_COLUMNS].tolist()
        print(f"\nFeatures used: {feature_names}\n")
        return feature_names

    def random_sample_testing(self, window, data, feature_names):
        """Perform the same random testing section and prints identical output."""
        print("="*60)
        print("RANDOM SAMPLE TESTING (10 samples)")
        print("="*60)

        np.random.seed(42)
        random_indices = np.random.choice(len(data), 20, replace=False)
        correct = 0

        labels = {0: 'Clean', 1: 'Static Spoofing', 2: 'Dynamic Spoofing'}

        for i, idx in enumerate(random_indices, 1):
            sample = data.iloc[[idx]][feature_names]
            actual = int(data.iloc[idx, 0])
            prediction, pred_num = self.predict_spoofing(sample, show_details=False)

            match = "✓" if pred_num == actual else "✗"
            if pred_num == actual:
                correct += 1

            if window:
                extra_cols = ['Label', 'lat', 'lon', 'alt', 'satellites_used']  # customize this list
                available = [c for c in extra_cols if c in data.columns]
                
                meta = data.loc[idx, available].to_dict() if available else {}

                print('meta: ', meta)
                # convert to JSON string so JS can parse it safely
                js_cmd = f'addData({json.dumps(meta)})'

                # Push to the webview. Use the window object returned by create_window.
                # This runs from a background thread and is allowed.
                window.evaluate_js(js_cmd)

                # simulate work
                time.sleep(1)

            print(f"{i:2d}. Actual: {labels[actual]:20s} | Predicted: {prediction:20s} {match}")

        if window:
            # notify JS that we're done
            window.evaluate_js("addData('Done producing data.')")
        
        print(f"\nAccuracy: {correct}/10 = {correct*10}%")

app.py:


from pathlib import Path
import time
import json
from functools import partial
import threading
import webview
from  GPStest import GPSSpoofingDetector


class Api:
    def __init__(self, detector, data, feature_names, window:None|webview.Window):
        self.detector = detector
        self.data = data
        self.feature_names = feature_names
        self.window = window

    def run_test(self):
        # This will be called from JS
        # Start a thread so UI doesn’t freeze
        import threading
        t = threading.Thread(target=detector.random_sample_testing, args=(window, data, feature_names))
        t.start()
        return "Started"

if __name__ == '__main__':
    detector = GPSSpoofingDetector()

    detector.load_model()
    data = detector.load_data()
    feature_names = detector.print_features_used(data)

    api = Api(detector, data, feature_names, None)

    window = webview.create_window('Live updates demo', url="app2.html", js_api=api)
    api.window = window

    print('starting webview')
    webview.start(http_server=True)

app2.html:


<head> 
    
<script>
  document.addEventListener('DOMContentLoaded', function() {
  document.getElementById('pybutn').addEventListener('click', function() {
    if (window.pywebview && window.pywebview.api) {
      window.pywebview.api.run_test();
    } else {
      console.warn('pywebview API not ready yet');
      setTimeout(()=> window.pywebview?.api?.run_test(), 300);
    }
  });



    // called by Python via window.evaluate_js("addData(...);")
    window.addData = function(obj) {
      const out = document.getElementById('out');
      const d = document.createElement('div');
      d.className = 'item';

      if (typeof obj === 'object' && obj !== null) {
          // You can access individual fields:
          d.innerHTML = `
          <b>Label:</b> ${obj.Label ?? 'N/A'}<br>
          <b>Lat:</b> ${obj.lat ?? 'N/A'}<br>
          <b>Lon:</b> ${obj.lon ?? 'N/A'}<br>
          <b>Alt:</b> ${obj.alt ?? 'N/A'}<br>
          <b>Sats:</b> ${obj.satellites_used ?? 'N/A'}
          `;
      } else {
        // obj might already be an object if evaluate_js passes it, but we send JSON string
        d.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj);
      }

      out.appendChild(d);
      out.scrollTop = out.scrollHeight; // auto-scroll
    }
  });
</script>
</head>

error:

initializing
Loading model...
✓ Model loaded successfully


Features used: ['time_utc_usec', 's_variance_m_s', 'c_variance_rad', 'epv', 'hdop', 'vdop', 'noise_per_ms', 'jamming_indicator', 'vel_m_s', 'vel_n_m_s', 'vel_e_m_s', 'vel_d_m_s', 'cog_rad', 'satellites_used']

starting webview
[pywebview] Error while processing data.Label.array.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T ...

What I expect:
  1. GPSSpoofingDetector loads model & CSV at script start (console prints show that).

  2. The web UI starts and waits for me to click "Start".

  3. When clicking "Start", JS calls window.pywebview.api.run_test().

  4. run_test() starts a background thread that runs detector.random_sample_testing(...). That method pushes JSON to the page via window.evaluate_js(...) and I see live updates in the page.

What actually happens:

The console output shows the model is loaded and features printed, then starting webview, but immediately I get long pywebview errors before I press Start

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.