Skip to content

Commit 7aa625d

Browse files
committed
linting
1 parent bd99bfb commit 7aa625d

File tree

3 files changed

+116
-100
lines changed

3 files changed

+116
-100
lines changed

src/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@
77

88
from .models.image_dir import imageDir
99

10-
Registry.register_resource_creator(Camera.API, imageDir.MODEL, ResourceCreatorRegistration(imageDir.new, imageDir.validate))
10+
Registry.register_resource_creator(
11+
Camera.API, imageDir.MODEL, ResourceCreatorRegistration(imageDir.new, imageDir.validate)
12+
)

src/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from viam.components.camera import Camera
66
from .models.image_dir import imageDir
77

8+
89
async def main():
910
"""This function creates and starts a new module, after adding all desired resources.
1011
Resources must be pre-registered. See the `__init__.py` file.
@@ -13,5 +14,6 @@ async def main():
1314
module.add_model_from_registry(Camera.API, imageDir.MODEL)
1415
await module.start()
1516

17+
1618
if __name__ == "__main__":
1719
asyncio.run(main())

src/models/image_dir.py

Lines changed: 111 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -22,100 +22,110 @@
2222
from viam.errors import ViamError, NotSupportedError
2323

2424
import os
25-
import io
25+
import io
2626
from datetime import datetime, timezone
2727

28-
import re
28+
import re
2929

3030
LOGGER = getLogger(__name__)
3131

32-
class imageDir(Camera, Reconfigurable):
3332

33+
class imageDir(Camera, Reconfigurable):
3434
class Properties(NamedTuple):
3535
supports_pcd: bool = False
3636
intrinsic_parameters = None
3737
distortion_parameters = None
3838

3939
MODEL: ClassVar[Model] = Model(ModelFamily("viam-labs", "camera"), "image-dir")
40-
40+
4141
camera_properties: Camera.Properties = Properties()
4242
# will store current get_image index for a given directory here
4343
directory_index: dict
44-
root_dir: str = '/tmp'
45-
ext: str = 'jpg'
44+
root_dir: str = "/tmp"
45+
ext: str = "jpg"
4646
dir: str
4747

4848
# Constructor
4949
@classmethod
50-
def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
50+
def new(
51+
cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]
52+
) -> Self:
5153
my_class = cls(config.name)
5254
my_class.reconfigure(config, dependencies)
5355
return my_class
5456

5557
# Validates JSON Configuration
5658
@classmethod
5759
def validate(cls, config: ComponentConfig):
58-
root_dir = config.attributes.fields["root_dir"].string_value or '/tmp'
60+
root_dir = config.attributes.fields["root_dir"].string_value or "/tmp"
5961
if not os.path.isdir(root_dir):
6062
raise Exception("specified 'root_dir' does not exist")
6163
return
6264

6365
# Handles attribute reconfiguration
64-
def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):
66+
def reconfigure(
67+
self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]
68+
):
6569
self.directory_index = {}
66-
self.root_dir = config.attributes.fields["root_dir"].string_value or '/tmp'
67-
self.ext = config.attributes.fields["ext"].string_value or 'jpg'
70+
self.root_dir = config.attributes.fields["root_dir"].string_value or "/tmp"
71+
self.ext = config.attributes.fields["ext"].string_value or "jpg"
6872
self.dir = config.attributes.fields["dir"].string_value
6973

7074
return
71-
72-
async def get_image(self, mime_type: str = "image/jpeg", *,
73-
timeout: Optional[float] = None,
74-
extra: Optional[Mapping[str, Any]] = None,
75-
metadata: Optional[Mapping[str, Any]] = None,
76-
**kwargs) -> ViamImage:
75+
76+
async def get_image(
77+
self,
78+
mime_type: str = "image/jpeg",
79+
*,
80+
timeout: Optional[float] = None,
81+
extra: Optional[Mapping[str, Any]] = None,
82+
metadata: Optional[Mapping[str, Any]] = None,
83+
**kwargs,
84+
) -> ViamImage:
7785
if extra is None:
7886
extra = {}
7987

80-
if extra.get('dir') == None:
88+
if extra.get("dir") == None:
8189
if self.dir == None:
82-
raise ViamError("'dir' must be passed in with 'extra', specifying image directory relative to the configured 'root_dir'")
90+
raise ViamError(
91+
"'dir' must be passed in with 'extra', specifying image directory relative to the configured 'root_dir'"
92+
)
8393
else:
84-
extra['dir'] = self.dir
85-
requested_dir = os.path.join(self.root_dir, extra['dir'])
86-
94+
extra["dir"] = self.dir
95+
requested_dir = os.path.join(self.root_dir, extra["dir"])
96+
8797
if not os.path.isdir(requested_dir):
8898
raise ViamError("requested 'dir' not found within configured 'root_dir'")
89-
99+
90100
image_index: int
91-
if extra.get('index') != None:
92-
image_index = extra['index']
93-
94-
if extra.get('index_reset') != None:
95-
if extra['index_reset'] == True:
101+
if extra.get("index") != None:
102+
image_index = extra["index"]
103+
104+
if extra.get("index_reset") != None:
105+
if extra["index_reset"] == True:
96106
# reset
97107
image_index = self._get_oldest_image_index(requested_dir)
98-
99-
if extra.get('index_jog') != None:
100-
image_index = self._jog_index(extra['index_jog'], requested_dir)
108+
109+
if extra.get("index_jog") != None:
110+
image_index = self._jog_index(extra["index_jog"], requested_dir)
101111
elif self.directory_index.get(requested_dir) != None:
102112
image_index = self.directory_index[requested_dir]
103113
else:
104114
image_index = self._get_oldest_image_index(requested_dir)
105-
115+
106116
ext = self.ext
107-
if extra.get('ext') != None:
108-
if extra['ext'] in ['jpg', 'jpeg', 'png', 'gif']:
109-
ext = extra['ext']
110-
117+
if extra.get("ext") != None:
118+
if extra["ext"] in ["jpg", "jpeg", "png", "gif"]:
119+
ext = extra["ext"]
120+
111121
# Get max index to handle wraparound
112122
max_index = self._get_greatest_image_index(requested_dir)
113-
123+
114124
# Wrap around if we've gone past the end
115125
if image_index > max_index:
116126
image_index = 0
117127
LOGGER.info(f"Reached end of directory, wrapping to index 0")
118-
128+
119129
file_path = self._get_file_path(requested_dir, image_index, ext)
120130
if not os.path.isfile(file_path):
121131
if extra.get("index"):
@@ -124,66 +134,70 @@ async def get_image(self, mime_type: str = "image/jpeg", *,
124134
else:
125135
# loop back to 0 index, we might be at the last image in dir
126136
image_index = 0
127-
file_path = os.path.join(requested_dir, str(image_index) + '.' + ext)
137+
file_path = os.path.join(requested_dir, str(image_index) + "." + ext)
128138
if not os.path.isfile(file_path):
129139
raise ViamError("No image at 0 index for " + file_path)
130-
140+
131141
img = Image.open(file_path)
132142
# LOGGER.info(f"Serving image {file_path} for index {image_index}")
133143

134144
# increment for next get_image() call
135145
self.directory_index[requested_dir] = image_index + 1
136-
137-
return pil_to_viam_image(img.convert('RGB'), CameraMimeType.from_string(mime_type))
146+
147+
return pil_to_viam_image(img.convert("RGB"), CameraMimeType.from_string(mime_type))
138148

139149
def _parse_timestamp_from_filename(self, filename: str) -> Optional[datetime]:
140150
"""
141151
Parse timestamp from filename format:
142152
2025-10-09T15_27_01.690Z_<hash>.jpeg
143-
153+
144154
Returns None if no timestamp can be parsed.
145155
"""
146156
# Remove extension
147157
name_without_ext = os.path.splitext(filename)[0]
148-
158+
149159
# Pattern: YYYY-MM-DDTHH_MM_SS.mmmZ
150160
# Example: 2025-10-09T15_27_01.690Z
151-
pattern = r'^(\d{4})-(\d{2})-(\d{2})T(\d{2})_(\d{2})_(\d{2})\.(\d{3})Z'
161+
pattern = r"^(\d{4})-(\d{2})-(\d{2})T(\d{2})_(\d{2})_(\d{2})\.(\d{3})Z"
152162
match = re.match(pattern, name_without_ext)
153-
163+
154164
if match:
155165
year, month, day, hour, minute, second, millisecond = match.groups()
156166
try:
157167
dt = datetime(
158-
int(year), int(month), int(day),
159-
int(hour), int(minute), int(second),
168+
int(year),
169+
int(month),
170+
int(day),
171+
int(hour),
172+
int(minute),
173+
int(second),
160174
int(millisecond) * 1000, # Convert milliseconds to microseconds
161-
tzinfo=timezone.utc
175+
tzinfo=timezone.utc,
162176
)
163177
return dt
164178
except ValueError as e:
165179
LOGGER.warning(f"Failed to parse timestamp from {filename}: {e}")
166180
return None
167-
181+
168182
return None
169183

170184
def _get_sorted_files(self, dir, ext):
171185
"""
172186
Get all files in directory, sorted by timestamp (if parseable) or mtime.
173187
Returns list of filenames in chronological order.
174188
"""
175-
files = [f for f in os.listdir(dir) if f.endswith(f'.{ext}')]
189+
files = [f for f in os.listdir(dir) if f.endswith(f".{ext}")]
176190
if not files:
177191
return []
178-
192+
179193
# Sort by parsed timestamp, fall back to mtime
180194
def sort_key(f):
181195
parsed_time = self._parse_timestamp_from_filename(f)
182196
if parsed_time:
183197
return parsed_time.timestamp()
184198
# Fall back to file modification time
185199
return os.stat(os.path.join(dir, f)).st_mtime
186-
200+
187201
files.sort(key=sort_key)
188202
return files
189203

@@ -193,93 +207,91 @@ def _get_file_path(self, dir, index, ext):
193207
if not sorted_files or index >= len(sorted_files):
194208
return None
195209
return os.path.join(dir, sorted_files[index])
196-
210+
197211
def _get_oldest_image_index(self, requested_dir):
198212
"""Return index 0 (oldest file after sorting)."""
199213
return 0
200-
214+
201215
def _get_greatest_image_index(self, requested_dir):
202216
"""Get the maximum valid index (count of files - 1)."""
203217
sorted_files = self._get_sorted_files(requested_dir, self.ext)
204218
if not sorted_files:
205219
return 0
206220
return len(sorted_files) - 1
207-
221+
208222
def _jog_index(self, index_jog, requested_dir):
209223
"""Move index forward or backward, wrapping around."""
210224
current_index = self.directory_index.get(requested_dir, 0)
211225
requested_index = current_index + index_jog
212226
max_index = self._get_greatest_image_index(requested_dir)
213-
227+
214228
if max_index == 0:
215229
return 0
216-
230+
217231
# Wrap around if out of bounds
218-
return requested_index % (max_index + 1)
219-
220-
async def get_images(self, *, timeout: Optional[float] = None,
221-
metadata: Optional[Mapping[str, Any]] = None,
222-
extra: Optional[Mapping[str, Any]] = None,
223-
filter_source_names: Optional[List[str]] = None,
224-
**kwargs) -> Tuple[List[NamedImage], ResponseMetadata]:
225-
232+
return requested_index % (max_index + 1)
233+
234+
async def get_images(
235+
self,
236+
*,
237+
timeout: Optional[float] = None,
238+
metadata: Optional[Mapping[str, Any]] = None,
239+
extra: Optional[Mapping[str, Any]] = None,
240+
filter_source_names: Optional[List[str]] = None,
241+
**kwargs,
242+
) -> Tuple[List[NamedImage], ResponseMetadata]:
226243
if extra is None:
227244
extra = {}
228-
245+
229246
# Determine source name
230247
source_name = self.dir or ""
231-
248+
232249
# Apply filtering if specified
233250
if filter_source_names is not None and len(filter_source_names) > 0:
234251
# If filtering is requested and our source isn't in the list, return empty
235252
if source_name not in filter_source_names:
236253
return [], ResponseMetadata()
237-
254+
238255
# Get the image
239256
image = await self.get_image(extra=extra, timeout=timeout)
240-
257+
241258
# Create NamedImage
242-
named_image = NamedImage(
243-
name=source_name,
244-
data=image.data,
245-
mime_type=image.mime_type
246-
)
247-
259+
named_image = NamedImage(name=source_name, data=image.data, mime_type=image.mime_type)
260+
248261
# Return with metadata
249262
return [named_image], ResponseMetadata()
250-
263+
251264
async def get_point_cloud(
252265
self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs
253266
) -> Tuple[bytes, str]:
254267
raise NotImplementedError()
255268

256269
# Implements the do_command which will respond to a map with key "request"
257-
async def do_command(self, command: Mapping[str, ValueTypes], *,
258-
timeout: Optional[float] = None,
259-
**kwargs) -> Mapping[str, ValueTypes]:
270+
async def do_command(
271+
self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs
272+
) -> Mapping[str, ValueTypes]:
260273
ret = {}
261-
if command.get('set') != None:
262-
setDict = command.get('set')
263-
if setDict.get('dir') != None:
264-
self.dir = setDict.get('dir')
274+
if command.get("set") != None:
275+
setDict = command.get("set")
276+
if setDict.get("dir") != None:
277+
self.dir = setDict.get("dir")
265278
requested_dir = os.path.join(self.root_dir, self.dir)
266-
if setDict.get('ext') != None:
267-
self.ext = setDict.get('ext')
268-
if setDict.get('index') != None:
269-
if isinstance(setDict['index'], int):
270-
self.directory_index[requested_dir] = setDict['index']
271-
if setDict.get('index_reset') != None:
272-
if setDict['index_reset'] == True:
279+
if setDict.get("ext") != None:
280+
self.ext = setDict.get("ext")
281+
if setDict.get("index") != None:
282+
if isinstance(setDict["index"], int):
283+
self.directory_index[requested_dir] = setDict["index"]
284+
if setDict.get("index_reset") != None:
285+
if setDict["index_reset"] == True:
273286
# reset
274287
index = self._get_oldest_image_index(requested_dir)
275-
self.directory_index[requested_dir] = index
276-
ret = { "index" : index }
277-
if setDict.get('index_jog') != None:
278-
index = self._jog_index(setDict['index_jog'], requested_dir)
288+
self.directory_index[requested_dir] = index
289+
ret = {"index": index}
290+
if setDict.get("index_jog") != None:
291+
index = self._jog_index(setDict["index_jog"], requested_dir)
279292
self.directory_index[requested_dir] = index
280-
ret = { "index" : index }
293+
ret = {"index": index}
281294
return ret
282-
283-
async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties:
284-
return self.camera_properties
285295

296+
async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties:
297+
return self.camera_properties

0 commit comments

Comments
 (0)