2929LOGGER = getLogger (__name__ )
3030SUPPORTED_EXTS = {"jpg" , "jpeg" , "png" , "gif" }
3131
32-
3332class imageDir (Camera , Reconfigurable ):
3433 class Properties (NamedTuple ):
3534 supports_pcd : bool = False
@@ -43,11 +42,11 @@ class Properties(NamedTuple):
4342 directory_index : dict
4443 root_dir : str = "/tmp"
4544 ext : str = "jpg"
46- dir : str
45+ sub_dir : str = ""
4746
4847 # Precomputed once per configured (dir, ext)
49- sorted_files : List [str ]
50- dir_len : int
48+ sorted_files : List [str ] = []
49+ sub_dir_len : int = 0
5150
5251 # Constructor
5352 @classmethod
@@ -99,15 +98,21 @@ def reconfigure(
9998 attrs = config .attributes .fields
10099 self .root_dir = attrs .get ("root_dir" ).string_value if "root_dir" in attrs else "/tmp"
101100 self .ext = (attrs .get ("ext" ).string_value if "ext" in attrs else "jpg" ).lower ()
102- self .dir = attrs .get ("dir" ).string_value if "dir" in attrs else ""
101+ self .sub_dir = attrs .get ("dir" ).string_value if "dir" in attrs else ""
102+
103+ # Log after attributes are set
104+ LOGGER .info (
105+ "image-dir: reconfigured name=%s root=%s sub_dir=%s ext=%s" ,
106+ self .name , self .root_dir , self .sub_dir , self .ext
107+ )
103108
104- requested_dir = os .path .join (self .root_dir , self .dir ) if self .dir else self .root_dir
109+ requested_dir = os .path .join (self .root_dir , self .sub_dir ) if self .sub_dir else self .root_dir
105110
106111 # Build the fixed, chronologically sorted image list (files in the directory) once
107112 self .sorted_files = self ._get_sorted_files (requested_dir , self .ext )
108- self .dir_len = len (self .sorted_files )
113+ self .sub_dir_len = len (self .sorted_files )
109114
110- if self .dir_len == 0 :
115+ if self .sub_dir_len == 0 :
111116 raise ViamError (f"No images with valid timestamp or numeric index found in { requested_dir } " )
112117
113118 self .directory_index = {requested_dir : 0 }
@@ -120,24 +125,22 @@ async def get_image(
120125 metadata : Optional [Mapping [str , Any ]] = None ,
121126 ** kwargs ,
122127 ) -> ViamImage :
123-
124128 extra = extra or {}
125-
129+
126130 # This code prevents changing dir/ext on a per-call basis
127131 # Enforces reconfigure() for such changes
128- if extra .get ("dir" ) and extra ["dir" ] != self .dir :
132+ if extra .get ("dir" ) and extra ["dir" ] != self .sub_dir :
129133 raise ViamError ("Per-call 'dir' override not supported; reconfigure instead." )
130134 if extra .get ("ext" ) and extra ["ext" ].lower () != self .ext :
131135 raise ViamError ("Per-call 'ext' override not supported; reconfigure instead." )
132136
133- requested_dir = os .path .join (self .root_dir , self .dir )
137+ requested_dir = os .path .join (self .root_dir , self .sub_dir )
134138 if not os .path .isdir (requested_dir ):
135139 raise ViamError ("configured directory no longer exists" )
136- if self .dir_len == 0 or not self .sorted_files :
140+ if self .sub_dir_len == 0 or not self .sorted_files :
137141 raise ViamError ("No images preloaded. Did reconfigure fail?" )
138142
139143 image_index : int
140-
141144
142145 # Direct index setting
143146 if extra .get ("index" ) is not None :
@@ -154,22 +157,37 @@ async def get_image(
154157 # Default: Start at 0
155158 else :
156159 image_index = 0
157-
160+
158161 # Wrap with precomputed length and use precomputed sorted list
159- n = self .dir_len
162+ n = self .sub_dir_len
160163 image_index = int (image_index ) % n
161-
164+
162165 file_path = os .path .join (requested_dir , self .sorted_files [image_index ])
163166 if not os .path .isfile (file_path ):
164167 raise ViamError (f"Image file not found: { file_path } " )
165-
166- # Open safely and convert to ViamImage
168+
169+ # Resolve mime type
170+ if isinstance (mime_type , CameraMimeType ):
171+ mt = mime_type
172+ else :
173+ mt_str = mime_type .strip ().lower () if mime_type is not None else ""
174+ # treat these as "no preference" and pick by ext
175+ if mt_str in {"" , "-" }:
176+ mt_str = "image/png" if self .ext .lower () == "png" else "image/jpeg"
177+ if mt_str == "image/jpg" :
178+ mt_str = "image/jpeg"
179+ try :
180+ mt = CameraMimeType .from_string (mt_str )
181+ except Exception :
182+ LOGGER .warning ("Unsupported mime '%s'; defaulting to image/jpeg" , mt_str )
183+ mt = CameraMimeType .JPEG
184+
167185 with Image .open (file_path ) as img :
168- vi_img = pil_to_viam_image (img .convert ("RGB" ), CameraMimeType . from_string ( mime_type ) )
169-
186+ vi_img = pil_to_viam_image (img .convert ("RGB" ), mt )
187+
170188 # Increment safely with modulo
171189 self .directory_index [requested_dir ] = (image_index + 1 ) % n
172-
190+
173191 return vi_img
174192
175193
@@ -237,18 +255,17 @@ def _get_sorted_files(self, dir, ext):
237255 idx .append ((f , key ))
238256 # else: unknown for now; we may warn after choosing mode
239257
240- # Choose majority; tie → timestamp
258+ # Sort by majority if mixed filenames: tie → timestamp
241259 if len (ts ) >= len (idx ) and len (ts ) > 0 :
242- LOGGER .info (f"Sorting mode: timestamp ({ len (ts )} files)" )
243260 keep = {f for f , _ in ts }
244261 for f in files :
245262 if f not in keep :
246263 LOGGER .warning (f"Skipping file without parsable timestamp: { f } " )
247264 ts .sort (key = lambda x : (x [1 ], x [0 ])) # stable & deterministic
248265 return [f for f , _ in ts ]
249266
267+ # Sort by numeric index
250268 if len (idx ) > 0 :
251- LOGGER .info (f"Sorting mode: index ({ len (idx )} files)" )
252269 keep = {f for f , _ in idx }
253270 for f in files :
254271 if f not in keep :
@@ -261,9 +278,8 @@ def _get_sorted_files(self, dir, ext):
261278 LOGGER .warning (f"Skipping file without timestamp or numeric index: { f } " )
262279 return []
263280
264-
265281 def _jog_index (self , index_jog , requested_dir ):
266- n = self .dir_len if self .dir_len > 0 else 1
282+ n = self .sub_dir_len if self .sub_dir_len > 0 else 1
267283 current_index = self .directory_index .get (requested_dir , 0 ) % n
268284
269285 return (current_index + int (index_jog )) % n
@@ -281,7 +297,7 @@ async def get_images(
281297 extra = {}
282298
283299 # Determine source name
284- source_name = self .dir or ""
300+ source_name = self .sub_dir or ""
285301
286302 # Apply filtering if specified
287303 if filter_source_names is not None and len (filter_source_names ) > 0 and source_name not in filter_source_names :
@@ -312,10 +328,10 @@ async def do_command(
312328 if setDict .get ("dir" ) is not None or setDict .get ("ext" ) is not None :
313329 raise ViamError ("Changing 'dir' or 'ext' requires a reconfigure." )
314330
315- requested_dir = os .path .join (self .root_dir , self .dir )
331+ requested_dir = os .path .join (self .root_dir , self .sub_dir )
316332
317333 if setDict .get ("index" ) is not None :
318- n = max (1 , self .dir_len )
334+ n = max (1 , self .sub_dir_len )
319335 self .directory_index [requested_dir ] = setDict ["index" ] % n
320336 ret = {"index" : self .directory_index [requested_dir ]}
321337
0 commit comments