11import asyncio
22import os
3+ import logging
34from datetime import datetime
4- from glob import glob
5-
6- # from arroyopy.schemas import NumpyArrayModel
7- import msgpack
85import typer
96import zmq
107import zmq .asyncio
11- from PIL import Image
8+ import msgpack
9+ from tiled .client import from_uri
1210
1311from ..config import settings
1412from ..schemas import (
1917)
2018
2119"""
22- Simulates however we are going to get images and sends them
23- onto ZMQ, taking care of pydantic messages, serialization and msgpack
20+ Simulates image retrieval from Tiled and sends them onto ZMQ,
21+ taking care of pydantic messages, serialization and msgpack
2422"""
2523
26- FRAME_WIDTH = 1475
27- FRAME_HEIGHT = 619
28- DATA_TYPE = "float32"
24+ # Default Tiled configuration - use the exact URI format from data_simulator.py
25+ DATA_TILED_URI = (
26+ "https://tiled-demo.blueskyproject.io/api/v1/metadata/rsoxs/raw/"
27+ "468810ed-2ff9-4e92-8ca9-dcb376d01a56/primary/data/Small Angle CCD Detector_image"
28+ )
29+ TILED_API_KEY = os .getenv ("DATA_TILED_KEY" )
30+ if TILED_API_KEY == "" :
31+ TILED_API_KEY = None
32+
33+ # Frame configuration
34+ FRAME_WIDTH = 1024
35+ FRAME_HEIGHT = 1026
36+ DATA_TYPE = "uint32"
37+
38+ # Setup logging
39+ logger = logging .getLogger (__name__ )
40+ logging .basicConfig (level = logging .INFO )
2941
3042app = typer .Typer ()
3143
3244
33- async def process_images (
34- socket : zmq .asyncio .Socket , cycles : int , frames : int , pause : float
45+ def get_num_frames (tiled_uri , tiled_api_key = None ):
46+ """Get the number of available frames from Tiled, following data_simulator.py pattern"""
47+ client = from_uri (tiled_uri , api_key = tiled_api_key )
48+ return client .shape [0 ] if hasattr (client , 'shape' ) and len (client .shape ) > 0 else 0
49+
50+
51+ async def process_images_from_tiled (
52+ socket : zmq .asyncio .Socket ,
53+ cycles : int ,
54+ frames : int ,
55+ pause : float ,
56+ tiled_uri : str ,
57+ tiled_api_key : str = None
3558):
36- for cycle_num in range (cycles ):
37- # Get current time formatted as YYYY-MM-DD HH:MM:SS
38- current_time = datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
39- start = SASStart (
40- width = FRAME_WIDTH ,
41- height = FRAME_HEIGHT ,
42- data_type = DATA_TYPE ,
43- tiled_url = "http://tiled:8000" ,
44- run_name = "test_run" ,
45- run_id = str (current_time ),
46- )
47- print ("start" )
48- await socket .send (msgpack .packb (start .model_dump ()))
49- files = glob ("/data/test_data/blade/*.tif" )
50- frame_num = 0
51- for file in files :
52- with os .read (file ) as filebytes :
53- image = Image .frombytes (filebytes )
54- event = RawFrameEvent (
55- image = SerializableNumpyArrayModel (array = image ),
56- frame_number = frame_num ,
57- tiled_url = "tb://frame_url" ,
59+ """
60+ Process images from Tiled and send them via ZMQ
61+ """
62+ try :
63+ # Connect to Tiled server using the exact URI format
64+ logger .info (f"Connecting to Tiled server at { tiled_uri } " )
65+ client = from_uri (tiled_uri , api_key = tiled_api_key )
66+
67+ # Get total number of available frames
68+ total_frames = get_num_frames (tiled_uri , tiled_api_key )
69+ logger .info (f"Total frames available in Tiled: { total_frames } " )
70+
71+ for cycle_num in range (cycles ):
72+ # Get current time formatted as YYYY-MM-DD HH:MM:SS
73+ current_time = datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
74+
75+ # Send SAS Start event
76+ start = SASStart (
77+ width = FRAME_WIDTH ,
78+ height = FRAME_HEIGHT ,
79+ data_type = DATA_TYPE ,
80+ tiled_url = tiled_uri ,
81+ run_name = f"tiled_run_{ cycle_num } " ,
82+ run_id = str (current_time ),
5883 )
59- print ("event" )
60- await socket .send (msgpack .packb (event .model_dump ()))
61- stop = SASStop (num_frames = frames )
62- print ("stop" )
63- await socket .send (msgpack .packb (stop .model_dump ()))
64- await asyncio .sleep (pause )
65- print (f"Cycle { cycle_num } complete sent { frames } frames" )
84+ logger .info (f"Sending start event for cycle { cycle_num } " )
85+ await socket .send (msgpack .packb (start .model_dump ()))
86+
87+ # Determine number of frames to process in this cycle
88+ frame_count = min (frames , total_frames )
89+ if frame_count == 0 :
90+ logger .warning ("No frames available in Tiled" )
91+ continue
92+
93+ # Process frames
94+ for frame_num in range (frame_count ):
95+ try :
96+ # Get frame data directly from Tiled client
97+ # This is the correct way to read data from a Tiled array client
98+ # The read() method returns a numpy array with the image data
99+ image_array = client [frame_num ]
100+
101+ # Create and send the frame event with the Tiled URI
102+ event = RawFrameEvent (
103+ image = SerializableNumpyArrayModel (array = image_array ),
104+ frame_number = frame_num ,
105+ tiled_url = f"{ tiled_uri } ?slice={ frame_num } " ,
106+ )
107+ logger .info (f"Sending frame { frame_num } for cycle { cycle_num } " )
108+ await socket .send (msgpack .packb (event .model_dump ()))
109+
110+ except Exception as e :
111+ logger .error (f"Error processing frame { frame_num } : { e } " )
112+
113+ # Send stop event
114+ stop = SASStop (num_frames = frame_count )
115+ logger .info (f"Sending stop event for cycle { cycle_num } " )
116+ await socket .send (msgpack .packb (stop .model_dump ()))
117+
118+ await asyncio .sleep (pause )
119+ logger .info (f"Cycle { cycle_num } complete - sent { frame_count } frames" )
66120
67- print ("All cycles complete" )
68- return
121+ logger .info ("All cycles complete" )
122+
123+ except Exception as e :
124+ logger .error (f"Error in processing images: { e } " )
69125
70126
71127@app .command ()
72- def main (cycles : int = 10000 , frames : int = 50 , pause : float = 5 ):
128+ def main (
129+ cycles : int = 10000 ,
130+ frames : int = 50 ,
131+ pause : float = 5 ,
132+ tiled_uri : str = None ,
133+ api_key : str = None
134+ ):
135+ """
136+ Run the image simulator that reads frames from Tiled and publishes them via ZMQ.
137+
138+ Args:
139+ cycles: Number of cycles to run
140+ frames: Maximum number of frames per cycle
141+ pause: Pause time between cycles in seconds
142+ tiled_uri: URI of the Tiled server (defaults to the predefined DATA_TILED_URI)
143+ api_key: API key for Tiled authentication (defaults to env var DATA_TILED_KEY)
144+ """
145+ # Use provided values or fall back to defaults
146+ tiled_uri = tiled_uri or DATA_TILED_URI
147+ api_key = api_key or TILED_API_KEY
148+
73149 async def run ():
74150 context = zmq .asyncio .Context ()
75151 socket = context .socket (zmq .PUB )
76152 address = settings .tiled_poller .publish_address
77- print (f"Connecting to { address } " )
153+ logger . info (f"Binding to ZMQ address: { address } " )
78154 socket .bind (address )
79- await process_images (socket , cycles , frames , pause )
155+ await process_images_from_tiled (socket , cycles , frames , pause , tiled_uri , api_key )
80156 return
81157
82158 asyncio .run (run ())
83159
84160
85161if __name__ == "__main__" :
86- app ()
162+ app ()
0 commit comments