diff --git a/app/main.py b/app/main.py
index 004dfcf..b79f346 100644
--- a/app/main.py
+++ b/app/main.py
@@ -11,6 +11,45 @@
from sync_link import SliceSynchronizer
+#####################################################################
+
+'''
+Syncronized zooming and updated zomm level
+'''
+class ZoomSync:
+ def __init__(self, slice1, slice2, scale_factor, slice1_caption, slice2_caption):
+ self.synchronizer = SliceSynchronizer(slice1, slice2, scale_factor) # Synchronizer for slices
+ self.synchronizer.register_callback(self.on_zoom_update)
+
+ # Store reference to captions for updates
+ self.slice1_caption = slice1_caption
+ self.slice2_caption = slice2_caption
+
+ def update_caption(self, caption, label, zoom_level):
+ """
+ Helper method to update captions dynamically.
+ """
+ caption.object = f"""
+
+
{label} Zoom Level: {round(zoom_level, 2)} %
+
+ """
+
+ def on_zoom_update(self, zoom_level_fig1, zoom_level_fig2):
+ """
+ Callback function to update zoom levels in captions.
+ """
+ print(f"Updated Zoom Levels - Fig1: {zoom_level_fig1}, Fig2: {zoom_level_fig2}")
+ self.update_caption(self.slice1_caption, slice1.image_type.value, zoom_level_fig1)
+ self.update_caption(self.slice2_caption, slice2.image_type.value, zoom_level_fig2)
+
+ def run(self):
+ """
+ Run the application.
+ """
+ print("Application is running...")
+
+
#####################################################################
def get_file_path():
@@ -34,6 +73,9 @@ def get_file_path():
logger.info(f"Command Line Args: {cmd_args}")
return cmd_args
+
+#####################################################################
+
if __name__.startswith('bokeh'):
pn.extension(
"ipywidgets",
@@ -52,10 +94,21 @@ def get_file_path():
# Show options for both slices
# "resolution" max , "view_dependent" is yes; set Default
- show_options = {
- "top": [["scene", "resolution","view_dependent"]], # color_mapper_type, Pallete, field, range mode max min are removed
- "bottom": [["request", "response"]],
- }
+ '''
+ show_options={
+ "top": [
+ [ "menu_button","scene", "timestep", "timestep_delta", "play_sec","play_button","palette", "color_mapper_type","view_dependent", "resolution", "num_refinements", "show_probe"],
+ ["field","direction", "offset", "range_mode", "range_min", "range_max"]
+
+ ],
+ "bottom": [
+ ["request","response", "zoom_level", "image_type"]
+ ]
+ }
+
+ '''
+
+ show_options = {}
args = get_file_path()
logger.info(f"Final Args Being Used: {args}")
@@ -64,10 +117,10 @@ def get_file_path():
logger.info(f"Using two-file sync view mode with files: {args[0]} and {args[1]}")
# Load for Sync Slices view
slice1 = Slice(ViewChoice="SYNC_VIEW")
- slice1.load(args[0])
+ slice1.load(sys.argv[1])
slice2 = Slice(ViewChoice="SYNC_VIEW")
- slice2.load(args[1])
+ slice2.load(sys.argv[2])
# Compute scale factor
@@ -75,6 +128,15 @@ def get_file_path():
box2 = slice2.db.getPhysicBox()
scale_factor = box2[0][1] / box1[0][1] # Scaling based on x-axis
+
+ if scale_factor < 1: # reverse both images
+ slice1.image_type.value = "Super-Resolution Image"
+ slice2.image_type.value = "Original Image"
+ else:
+ slice1.image_type.value = "Original Image"
+ slice2.image_type.value = "Super-Resolution Image"
+
+
slice1.setShowOptions(show_options)
slice2.setShowOptions(show_options)
@@ -86,12 +148,11 @@ def get_file_path():
f"""
-
Original Image ({int(box1[0][1])} x {int(box1[1][1])})
+ justify-content: top;
+ align-items: top;
+ text-align: top;
+ height: 5;">
+ Original Image
""",
sizing_mode="stretch_width", # Ensures full width for centering
@@ -101,34 +162,38 @@ def get_file_path():
f"""
-
Super-Resolution Image ({int(box2[0][1])} x {int(box2[1][1])}) Scale Factor: {scale_factor:.2f}
+ justify-content: top;
+ align-items: top;
+ text-align: top;
+ height: 5;">
+ Super-Resolution Image
""",
sizing_mode="stretch_width", # Ensures full width for centering
)
- # Synchronizer for slices
- synchronizer = SliceSynchronizer(slice1, slice2, scale_factor)
+ # Initialize the ZoomSync class
+ zoom_sync = ZoomSync(slice1, slice2, scale_factor, slice1_caption,slice2_caption)
+ zoom_sync.run()
# Add the buttons to the layout
- layout = pn.Row(
- pn.Column(
- slice1_caption,
- slice1.getMainLayout(),
+ layout = pn.Column(
+ pn.Row(
+ pn.Column(
+ slice1.getMainLayout(),
+ sizing_mode="stretch_both",
+ ),
+ pn.Column(
+ slice2.getMainLayout(),
+ sizing_mode="stretch_both",
+ ),
sizing_mode="stretch_both",
),
- pn.Column(
- slice2_caption,
- slice2.getMainLayout(),
+ pn.Row(
+ slice1_caption, slice2_caption,
sizing_mode="stretch_both",
- ),
- sizing_mode="stretch_both",
+ )
)
layout.servable()
else:
diff --git a/app/sync_link.py b/app/sync_link.py
index 15cb797..9e83b9c 100644
--- a/app/sync_link.py
+++ b/app/sync_link.py
@@ -1,5 +1,6 @@
from bokeh.models import CustomJS
from threading import Timer
+import math
class SliceSynchronizer:
@@ -7,8 +8,14 @@ def __init__(self, slice1, slice2, scale_factor=4):
self.slice1 = slice1
self.slice2 = slice2
self.scale_factor = scale_factor
+ self.full_width = None
self.debounce_timer = None # Timer to debounce refresh calls
self.debounce_delay = 0.1 # Delay in seconds for debounce
+ self.update_callbacks = [] # Callback list for external
+
+ # Store zoom levels
+ self.zoom_level_1 = 1.0 #intial value
+ self.zoom_level_2 = 1.0
# Link ranges with JavaScript callbacks
self.link_ranges()
@@ -64,6 +71,11 @@ def trigger_refresh():
self.slice1.refresh("DebouncedSyncFromSlice2")
self.slice2.refresh("DebouncedSyncFromSlice1")
+ self.zoom_level_1= self.update_zoom_level(fig1)
+ self.zoom_level_2= self.update_zoom_level(fig2)
+
+ self.notify_subscribers()
+
def debounce_refresh(attr, old, new):
"""Debounce refresh calls."""
if self.debounce_timer:
@@ -82,4 +94,40 @@ def debounce_refresh(attr, old, new):
fig2.x_range.on_change('start', debounce_refresh)
fig2.x_range.on_change('end', debounce_refresh)
fig2.y_range.on_change('start', debounce_refresh)
- fig2.y_range.on_change('end', debounce_refresh)
\ No newline at end of file
+ fig2.y_range.on_change('end', debounce_refresh)
+
+
+ #Zoom level Calculation
+ def update_zoom_level(self, fig):
+ """
+ Calculate and update the zoom level dynamically.
+ """
+ # Get current viewport size
+ image_width = fig.x_range.end - fig.x_range.start
+
+ # Prevent division by zero
+ image_width = max(1, image_width)
+
+ viewport_width = fig.inner_width
+
+ zoomLevel = (viewport_width / math.ceil(image_width)) * 100
+
+ print(f"zoom: viewport width={viewport_width}, image width={math.ceil(image_width)}, zoomLevel={zoomLevel}%")
+
+
+ return (zoomLevel) # Return the zoom level
+
+
+ def register_callback(self, callback):
+ """
+ Register a callback function to be notified when zoom levels are updated.
+ The callback function should accept two arguments: (zoom_level_fig1, zoom_level_fig2).
+ """
+ self.update_callbacks.append(callback)
+
+ def notify_subscribers(self):
+ """
+ Notify all registered subscribers with the updated zoom levels.
+ """
+ for callback in self.update_callbacks:
+ callback(self.zoom_level_1, self.zoom_level_2)
\ No newline at end of file
diff --git a/src/openvisuspy/slice.py b/src/openvisuspy/slice.py
index 28e784f..559f7ab 100644
--- a/src/openvisuspy/slice.py
+++ b/src/openvisuspy/slice.py
@@ -271,7 +271,7 @@ class Slice(param.Parameterized):
],
"bottom": [
- ["request","response"]
+ ["request","response","image_type"]
]
}
@@ -577,6 +577,8 @@ def onColorMapperTypeChange(evt):
self.request = pn.widgets.TextInput(name="", sizing_mode='stretch_width', disabled=False)
self.response = pn.widgets.TextInput(name="", sizing_mode='stretch_width', disabled=False)
+ self.image_type = pn.widgets.TextInput(name="", sizing_mode='stretch_width', disabled=False) # Sync-View Image Title
+
self.file_name_input = pn.widgets.TextInput(name="Numpy_File", value='test', placeholder='Numpy File Name to save')
self.canvas = Canvas(self.id, self.view_choice)