1111import time
1212import math
1313from urllib .parse import urlparse , urlencode
14- from typing import Optional
1514
1615import numpy as np
1716
4039from .backend import Aborted ,LoadDataset ,ExecuteBoxQuery
4140
4241from .show_details import ShowDetails
43- from .tile_cache import TileCache , TileKey , TileData
4442
4543logger = logging .getLogger (__name__ )
4644
@@ -384,7 +382,7 @@ class Slice(param.Parameterized):
384382 show_options = {
385383 "top" : [
386384 [ "menu_button" ,"scene" , "timestep" , "timestep_delta" , "play_sec" ,"play_button" ,"palette" , "color_mapper_type" ,"view_dependent" , "resolution" , "num_refinements" , "show_probe" ],
387- ["field" ,"direction" , "offset" , "range_mode" , "range_min" , "range_max" , "tile_cache_enabled" , "tile_cache_stats_btn" ]
385+ ["field" ,"direction" , "offset" , "range_mode" , "range_min" , "range_max" ]
388386
389387 ],
390388 "bottom" : [
@@ -417,17 +415,6 @@ def __init__(self, drawsource=None, ViewChoice=None):
417415 self .new_job = False
418416 self .current_img = None
419417 self .last_job_pushed = time .time ()
420- self .using_cached_display = False # Track if currently showing cached data
421- self .prefetched_tiles = set () # Track which tiles have already been prefetched
422- self .current_tile_key = None # Track the current displayed tile
423-
424- # Initialize tile cache - DISABLED for now as it causes stale display issues
425- self .tile_cache = TileCache (
426- max_tiles = 50 ,
427- prefetch_radius = 1 ,
428- enabled = False # DISABLED - caching causes display sync issues
429- )
430- self .tile_size = 1024
431418
432419
433420
@@ -636,45 +623,6 @@ def onFieldChange(evt):
636623 self .view_dependent = pn .widgets .Select (name = "ViewDep" , options = {"Yes" : True , "No" : False }, value = True , width = 80 )
637624 self .view_dependent .param .watch (SafeCallback (lambda evt : self .refresh ("view_dependent.param.watch" )),"value" , onlychanged = True ,queued = True )
638625
639- # Tile cache controls
640- self .tile_cache_enabled = pn .widgets .Toggle (
641- name = 'TileCache' ,
642- value = True ,
643- width = 90 ,
644- button_type = 'success'
645- )
646- def onTileCacheToggle (evt ):
647- self .tile_cache .set_enabled (evt .new )
648- if evt .new :
649- ShowInfoNotification ("Tile cache enabled - smoother panning!" )
650- else :
651- ShowInfoNotification ("Tile cache disabled" )
652- self .tile_cache .clear ()
653- # Trigger a refresh to reload with new cache state
654- self .refresh ("tile_cache_toggle" )
655- self .tile_cache_enabled .param .watch (SafeCallback (onTileCacheToggle ), "value" , onlychanged = True , queued = True )
656-
657- self .tile_cache_stats_btn = pn .widgets .Button (
658- name = '📊 Cache Stats' ,
659- width = 110 ,
660- button_type = 'light'
661- )
662- def showCacheStats (evt ):
663- stats = self .tile_cache .get_stats ()
664- msg = (
665- f"Tile Cache Statistics:\n "
666- f"Status: { 'Enabled' if stats ['enabled' ] else 'Disabled' } \n "
667- f"Cached tiles: { stats ['size' ]} /{ stats ['max_tiles' ]} \n "
668- f"Hit rate: { stats ['hit_rate' ]} \n "
669- f"Hits: { stats ['hits' ]} , Misses: { stats ['misses' ]} \n "
670- f"Prefetches: { stats ['prefetches' ]} \n "
671- f"Evictions: { stats ['evictions' ]} \n "
672- f"Loading: { stats ['loading' ]} tiles"
673- )
674- ShowInfoNotification (msg )
675- logger .info (msg )
676- self .tile_cache_stats_btn .on_click (SafeCallback (showCacheStats ))
677-
678626 self .num_refinements = pn .widgets .IntSlider (name = '#Ref' , value = 0 , start = 0 , end = 4 , width = 80 )
679627 self .num_refinements .param .watch (SafeCallback (lambda evt : self .refresh ("num_refinements.param.watch" )),"value" , onlychanged = True ,queued = True )
680628 self .direction = pn .widgets .Select (name = 'Direction' , options = {'X' : 0 , 'Y' : 1 , 'Z' : 2 }, value = 2 , width = 80 )
@@ -894,8 +842,6 @@ def CreateWidgets(row):
894842 if num_timesteps == 1 and widget in [self .timestep , self .timestep_delta , self .play_sec ,self .play_button ]:
895843 continue
896844 ret .append (widget )
897- else :
898- logger .warning (f"Widget '{ it } ' not found in Slice instance" )
899845
900846 return ret
901847
@@ -922,9 +868,6 @@ def getShareableUrl(self, short=True):
922868 # stop
923869 def stop (self ):
924870 self .aborted .setTrue ()
925- # Clean up tile cache
926- if hasattr (self , 'tile_cache' ):
927- self .tile_cache .clear ()
928871 if self .db :
929872 self .db .stop ()
930873
@@ -1420,132 +1363,11 @@ def setWidgetsDisabled(self, value):
14201363 def getPointDim (self ):
14211364 return self .db .getPointDim () if self .db else 2
14221365
1423- # Tile cache helper methods
1424- def _viewport_to_tile_key (self , viewport = None ) -> TileKey :
1425- """
1426- Convert viewport to tile coordinates for caching.
1427- Key represents the ACTUAL viewport data, not artificial tiles.
1428- """
1429- if viewport is None :
1430- viewport = self .canvas .getViewport ()
1431-
1432- x , y , w , h = viewport
1433-
1434- # Use viewport center and size with moderate rounding
1435- # This creates cache keys that represent actual visible areas
1436- center_x = x + w / 2
1437- center_y = y + h / 2
1438-
1439- # Round to create stable keys while allowing smooth panning
1440- # Use current viewport size as the grid (so one "tile" = one viewport)
1441- grid_size = max (w , h )
1442-
1443- # Snap center to grid (allows ~50% overlap before new key)
1444- tile_x = int (round (center_x / grid_size ))
1445- tile_y = int (round (center_y / grid_size ))
1446-
1447- # Use actual resolution (not rounded) for accurate zoom tracking
1448- zoom = int (self .resolution .value ) if hasattr (self , 'resolution' ) else 0
1449-
1450- # Include timestep and field
1451- timestep = int (self .timestep .value ) if hasattr (self , 'timestep' ) else 0
1452- field = self .field .value if hasattr (self , 'field' ) else ""
1453-
1454- return TileKey (x = tile_x , y = tile_y , z = zoom , timestep = timestep , field = field )
1455-
1456- def _tile_key_to_logic_box (self , tile_key : TileKey ):
1457- """
1458- Convert tile key back to logic box coordinates.
1459- This defines what portion of the image this tile represents.
1460- """
1461- # Calculate pixel-space viewport from tile coordinates
1462- pixel_x = tile_key .x * self .tile_size
1463- pixel_y = tile_key .y * self .tile_size
1464- pixel_viewport = [pixel_x , pixel_y , self .tile_size , self .tile_size ]
1465-
1466- # Convert to logic coordinates
1467- logic_box = self .toLogic (pixel_viewport )
1468-
1469- return logic_box
1470-
1471- def _load_tile_data (self , tile_key : TileKey ) -> Optional [TileData ]:
1472- """
1473- Load data for a specific tile key (actual viewport area).
1474- This loads the EXACT data that would be displayed for that viewport position.
1475- """
1476- try :
1477- # Reconstruct viewport from tile key
1478- # Tile key represents viewport center, so we need current viewport size
1479- canvas_w , canvas_h = self .canvas .getWidth (), self .canvas .getHeight ()
1480- if not canvas_w or not canvas_h :
1481- return None
1482-
1483- # Get current viewport to determine size
1484- current_viewport = self .canvas .getViewport ()
1485- _ , _ , w , h = current_viewport
1486-
1487- # Calculate viewport center from tile key
1488- grid_size = max (w , h )
1489- center_x = tile_key .x * grid_size
1490- center_y = tile_key .y * grid_size
1491-
1492- # Reconstruct viewport
1493- viewport = [
1494- center_x - w / 2 ,
1495- center_y - h / 2 ,
1496- w ,
1497- h
1498- ]
1499-
1500- # Convert to logic box - THIS is the actual query area
1501- logic_box = self .toLogic (viewport )
1502-
1503- # Validate prerequisites
1504- if not self .db or not self .access :
1505- return None
1506-
1507- # Execute query for this exact viewport
1508- max_pixels = int (canvas_w * canvas_h )
1509- endh = tile_key .z
1510- tile_aborted = Aborted ()
1511-
1512- result = ExecuteBoxQuery (
1513- db = self .db ,
1514- access = self .access ,
1515- timestep = tile_key .timestep ,
1516- field = tile_key .field ,
1517- logic_box = logic_box ,
1518- max_pixels = max_pixels ,
1519- num_refinements = 0 ,
1520- endh = endh ,
1521- aborted = tile_aborted
1522- )
1523-
1524- # Return valid data only
1525- if result and isinstance (result , dict ) and 'data' in result :
1526- data = result .get ('data' )
1527- if data is not None and hasattr (data , 'shape' ):
1528- return TileData (
1529- data = data ,
1530- logic_box = result .get ('logic_box' , logic_box ),
1531- timestamp = time .time ()
1532- )
1533-
1534- return None
1535-
1536- except (AttributeError , IndexError , TypeError , ValueError , KeyError ):
1537- return None
1538- except Exception :
1539- return None
1540-
15411366 # refresh
15421367 def refresh (self ,reason = None ):
15431368 logger .info (f"reason={ reason } " )
15441369 self .aborted .setTrue ()
15451370 self .new_job = True
1546- self .using_cached_display = False # Reset cache flag on refresh
1547- self .current_tile_key = None # Reset to allow new tile checks
1548-
15491371
15501372 # getQueryLogicBox
15511373 def getQueryLogicBox (self ):
@@ -1591,10 +1413,6 @@ def gotoPoint(self,point):
15911413
15921414 # gotNewData
15931415 def gotNewData (self , result ):
1594- # Don't overwrite cached displays with progressive results
1595- if self .using_cached_display :
1596- logger .debug ("Skipping progressive update - using cached display" )
1597- return
15981416
15991417 data = result ['data' ]
16001418 try :
@@ -1685,20 +1503,6 @@ def gotNewData(self, result):
16851503 (X ,Y ,Z ),(tX ,tY ,tZ )= self .getLogicAxis ()
16861504 self .canvas .setAxisLabels (tX ,tY )
16871505
1688- # Cache the rendered tile ONLY if query is complete (high quality)
1689- # Don't cache intermediate/progressive results
1690- if self .tile_cache .is_enabled () and not result .get ('running' , False ):
1691- try :
1692- current_tile_key = self ._viewport_to_tile_key ()
1693- self .tile_cache .put_tile (current_tile_key , data , logic_box )
1694- logger .debug (f"✓ Cached final tile: { current_tile_key } " )
1695-
1696- # Prefetch surrounding tiles if not already done
1697- if current_tile_key not in self .prefetched_tiles :
1698- self .tile_cache .prefetch_async (current_tile_key , self ._load_tile_data )
1699- self .prefetched_tiles .add (current_tile_key )
1700- except Exception as e :
1701- logger .debug (f"Tile caching error (non-critical): { e } " )
17021506
17031507 # update the status bar
17041508
@@ -1729,15 +1533,17 @@ def pushJobIfNeeded(self):
17291533 query_logic_box = self .getQueryLogicBox ()
17301534 pdim = self .getPointDim ()
17311535
1536+ # Check if user is actively panning (< 500ms since last request)
1537+ current_time = time .time ()
1538+ time_since_last = current_time - self .last_job_pushed
1539+ is_active_pan = time_since_last < 0.5
1540+
17321541 # abort the last one
17331542 self .aborted .setTrue ()
17341543 self .db .waitIdle ()
17351544
17361545 # Adaptive refinement based on pan activity
17371546 num_refinements = self .num_refinements .value
1738-
1739- # Use progressive rendering (0 = disabled, higher = more refinements)
1740- # Progressive shows low-res quickly, then refines
17411547 if num_refinements == 0 :
17421548 if is_active_pan :
17431549 # Fast single-pass during active panning
@@ -1751,8 +1557,8 @@ def pushJobIfNeeded(self):
17511557 }[pdim ]
17521558 self .aborted = Aborted ()
17531559
1754- # do not push too many jobs
1755- if ( time . time () - self . last_job_pushed ) < 0.2 :
1560+ # More aggressive throttling - reduced from 0.2s to 0.1s
1561+ if time_since_last < 0.1 :
17561562 return
17571563
17581564 # I will use max_pixels to decide what resolution, I am using resolution just to add/remove a little the 'quality'
@@ -1814,19 +1620,6 @@ def pushJobIfNeeded(self):
18141620 aborted = self .aborted
18151621 )
18161622
1817- # Start aggressive prefetching immediately (don't wait for this query to finish)
1818- # But ONLY if tile key changed (avoid redundant prefetch spam)
1819- if self .tile_cache .is_enabled () and canvas_w > 0 and canvas_h > 0 :
1820- try :
1821- current_tile_key = self ._viewport_to_tile_key ()
1822- if current_tile_key not in self .prefetched_tiles :
1823- # Prefetch surrounding tiles in background while main query runs
1824- self .tile_cache .prefetch_async (current_tile_key , self ._load_tile_data )
1825- self .prefetched_tiles .add (current_tile_key )
1826- logger .debug (f"Started prefetch for { current_tile_key } " )
1827- except Exception as e :
1828- logger .warning (f"Prefetch start error: { e } " )
1829-
18301623 self .last_job_pushed = time .time ()
18311624 self .new_job = False
18321625 # logger.debug(f"id={self.id} pushed new job query_logic_box={query_logic_box}")
0 commit comments