Skip to content

Commit 19f55cb

Browse files
committed
Enhance type hinting and logging in API handlers; add progress bar support in uploads
1 parent 50d30cd commit 19f55cb

3 files changed

Lines changed: 57 additions & 29 deletions

File tree

datamint/apihandler/annotation_api_handler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -995,11 +995,11 @@ def get_annotation_worklist_by_id(self,
995995

996996
def update_annotation_worklist(self,
997997
worklist_id: str,
998-
frame_labels: list[str] = None,
999-
image_labels: list[str] = None,
1000-
annotations: list[dict] = None,
1001-
status: Literal['new', 'updating', 'active', 'completed'] = None,
1002-
name: str = None,
998+
frame_labels: list[str] | None = None,
999+
image_labels: list[str] | None = None,
1000+
annotations: list[dict] | None = None,
1001+
status: Literal['new', 'updating', 'active', 'completed'] | None = None,
1002+
name: str | None = None,
10031003
):
10041004
"""
10051005
Update the status of an annotation worklist.

datamint/apihandler/root_api_handler.py

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
import asyncio
88
import aiohttp
9-
from medimgkit.dicom_utils import anonymize_dicom, to_bytesio, is_dicom, is_dicom_report
9+
from medimgkit.dicom_utils import anonymize_dicom, to_bytesio, is_dicom, is_dicom_report, GeneratorWithLength
1010
from medimgkit import dicom_utils, standardize_mimetype
1111
from medimgkit.io_utils import is_io_object, peek
1212
from medimgkit.format_detection import guess_typez, guess_extension, DEFAULT_MIME_TYPE
@@ -185,9 +185,7 @@ async def _upload_single_resource_async(self,
185185
resp_data = await self._run_request_async(request_params, session)
186186
if 'error' in resp_data:
187187
raise DatamintException(resp_data['error'])
188-
_LOGGER.info(f"Response on uploading {name}: {resp_data}")
189-
190-
_USER_LOGGER.info(f'"{name}" uploaded')
188+
_LOGGER.debug(f"Response on uploading {name}: {resp_data}")
191189
return resp_data['id']
192190
except Exception as e:
193191
if 'name' in locals():
@@ -212,6 +210,7 @@ async def _upload_resources_async(self,
212210
segmentation_files: Optional[list[dict]] = None,
213211
transpose_segmentation: bool = False,
214212
metadata_files: Optional[list[str | dict | None]] = None,
213+
progress_bar: tqdm | None = None,
215214
) -> list[str]:
216215
if on_error not in ['raise', 'skip']:
217216
raise ValueError("on_error must be either 'raise' or 'skip'")
@@ -225,6 +224,8 @@ async def _upload_resources_async(self,
225224
async with aiohttp.ClientSession() as session:
226225
async def __upload_single_resource(file_path, segfiles: dict[str, list | dict],
227226
metadata_file: str | dict | None):
227+
name = file_path.name if is_io_object(file_path) else file_path
228+
name = os.path.basename(name)
228229
rid = await self._upload_single_resource_async(
229230
file_path=file_path,
230231
mimetype=mimetype,
@@ -238,6 +239,12 @@ async def __upload_single_resource(file_path, segfiles: dict[str, list | dict],
238239
publish=publish,
239240
metadata_file=metadata_file,
240241
)
242+
if progress_bar:
243+
progress_bar.update(1)
244+
progress_bar.set_postfix(file=name)
245+
else:
246+
_USER_LOGGER.info(f'"{name}" uploaded')
247+
241248
if segfiles is not None:
242249
fpaths = segfiles['files']
243250
names = segfiles.get('names', _infinite_gen(None))
@@ -295,7 +302,9 @@ def _assemble_dicoms(self, files_path: Sequence[str | IO]
295302
if new_len != orig_len:
296303
_LOGGER.info(f"Assembled {new_len} dicom files out of {orig_len} files.")
297304
mapping_idx = [None] * len(files_path)
298-
files_path = itertools.chain(dicoms_files_path, other_files_path)
305+
306+
files_path = GeneratorWithLength(itertools.chain(dicoms_files_path, other_files_path),
307+
length=new_len + len(other_files_path))
299308
assembled = True
300309
for orig_idx, value in zip(dicom_original_idxs, dicoms_files_path.inverse_mapping_idx):
301310
mapping_idx[orig_idx] = value
@@ -391,7 +400,8 @@ def upload_resource(self,
391400
transpose_segmentation=transpose_segmentation,
392401
modality=modality,
393402
assemble_dicoms=assemble_dicoms,
394-
metadata=metadata
403+
metadata=metadata,
404+
progress_bar=False
395405
)
396406

397407
return result[0]
@@ -412,7 +422,8 @@ def upload_resources(self,
412422
modality: Optional[str] = None,
413423
assemble_dicoms: bool = True,
414424
metadata: list[str | dict | None] | dict | str | None = None,
415-
discard_dicom_reports: bool = True
425+
discard_dicom_reports: bool = True,
426+
progress_bar: bool = False
416427
) -> list[str | Exception] | str | Exception:
417428
"""
418429
Upload resources.
@@ -485,6 +496,11 @@ def upload_resources(self,
485496
assemble_dicoms = assembled
486497
else:
487498
mapping_idx = [i for i in range(len(files_path))]
499+
n_files = len(files_path)
500+
501+
if n_files <= 1:
502+
# Disable progress bar for single file uploads
503+
progress_bar = False
488504

489505
if segmentation_files is not None:
490506
if assemble_dicoms:
@@ -513,22 +529,32 @@ def upload_resources(self,
513529
"segmentation_files['names'] must have the same length as segmentation_files['files'].")
514530

515531
loop = asyncio.get_event_loop()
516-
task = self._upload_resources_async(files_path=files_path,
517-
mimetype=mimetype,
518-
anonymize=anonymize,
519-
anonymize_retain_codes=anonymize_retain_codes,
520-
on_error=on_error,
521-
tags=tags,
522-
mung_filename=mung_filename,
523-
channel=channel,
524-
publish=publish,
525-
segmentation_files=segmentation_files,
526-
transpose_segmentation=transpose_segmentation,
527-
modality=modality,
528-
metadata_files=metadata,
529-
)
530-
531-
resource_ids = loop.run_until_complete(task)
532+
pbar = None
533+
try:
534+
if progress_bar:
535+
pbar = tqdm(total=n_files, desc="Uploading resources", unit="file")
536+
537+
task = self._upload_resources_async(files_path=files_path,
538+
mimetype=mimetype,
539+
anonymize=anonymize,
540+
anonymize_retain_codes=anonymize_retain_codes,
541+
on_error=on_error,
542+
tags=tags,
543+
mung_filename=mung_filename,
544+
channel=channel,
545+
publish=publish,
546+
segmentation_files=segmentation_files,
547+
transpose_segmentation=transpose_segmentation,
548+
modality=modality,
549+
metadata_files=metadata,
550+
progress_bar=pbar
551+
)
552+
553+
resource_ids = loop.run_until_complete(task)
554+
finally:
555+
if pbar:
556+
pbar.close()
557+
532558
_LOGGER.info(f"Resources uploaded: {resource_ids}")
533559

534560
if publish_to is not None:

datamint/client_cmd_tools/datamint_upload.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# Create two loggings: one for the user and one for the developer
2323
_LOGGER = logging.getLogger(__name__)
2424
_USER_LOGGER = logging.getLogger('user_logger')
25+
logging.getLogger('pydicom').setLevel(logging.ERROR)
2526
CONSOLE: Console
2627

2728
MAX_RECURSION_LIMIT = 1000
@@ -778,7 +779,8 @@ def main():
778779
segmentation_files=segfiles,
779780
transpose_segmentation=args.transpose_segmentation,
780781
assemble_dicoms=True,
781-
metadata=metadata_files
782+
metadata=metadata_files,
783+
progress_bar=True
782784
)
783785
except pydicom.errors.InvalidDicomError as e:
784786
_USER_LOGGER.error(f'❌ Invalid DICOM file: {e}')

0 commit comments

Comments
 (0)