|
7 | 7 | from difflib import SequenceMatcher |
8 | 8 | from argparse import ArgumentParser, Namespace, ArgumentDefaultsHelpFormatter |
9 | 9 | from pydicom.pixel_data_handlers import convert_color_space |
| 10 | +from PIL import Image |
10 | 11 | from chris_plugin import chris_plugin, PathMapper |
| 12 | +from pydicom.pixel_data_handlers import convert_color_space |
11 | 13 | import pydicom as dicom |
| 14 | +import numpy as np |
| 15 | +import operator |
12 | 16 | import cv2 |
13 | 17 | import json |
14 | | -from pydicom.pixel_data_handlers import convert_color_space |
15 | | -import numpy as np |
16 | 18 | import re |
17 | 19 | import os |
18 | 20 | import sys |
19 | | -from PIL import Image |
20 | 21 |
|
21 | | -__version__ = '1.2.9' |
| 22 | + |
| 23 | +__version__ = '1.3.0' |
22 | 24 |
|
23 | 25 | DISPLAY_TITLE = r""" |
24 | 26 | _ _ _ __ _ _ _ |
|
39 | 41 | help='comma separated dicom tags with values') |
40 | 42 | parser.add_argument('-f', '--fileFilter', default='dcm', type=str, |
41 | 43 | help='input file filter glob') |
42 | | -parser.add_argument('-m', '--minImgCount', default=1, type=int, |
43 | | - help='A configurable threshold—any series with fewer images is dropped.') |
| 44 | +parser.add_argument('-m', '--imgCount', default=">=1", type=str, |
| 45 | + help=( |
| 46 | + "Image count filter expression. " |
| 47 | + "Supports multiple conditions combined with AND.\n" |
| 48 | + "Examples:\n" |
| 49 | + " '>=10 <=200'\n" |
| 50 | + " '>5 !=13'\n" |
| 51 | + " '==42'" |
| 52 | + )) |
44 | 53 | parser.add_argument('-V', '--version', action='version', |
45 | 54 | version=f'%(prog)s {__version__}') |
46 | 55 | parser.add_argument('-o', '--outputType', default='dcm', type=str, |
@@ -475,19 +484,67 @@ def zipper_mapper(mapper1, mapper2, fill_value=None): |
475 | 484 |
|
476 | 485 | yield (in1, in2, out1) |
477 | 486 |
|
| 487 | +OPS = { |
| 488 | + ">": operator.gt, |
| 489 | + ">=": operator.ge, |
| 490 | + "<": operator.lt, |
| 491 | + "<=": operator.le, |
| 492 | + "==": operator.eq, |
| 493 | + "!=": operator.ne, |
| 494 | +} |
| 495 | + |
| 496 | +_COND_RE = re.compile(r"(>=|<=|==|!=|>|<)\s*(\d+)$") |
| 497 | + |
| 498 | + |
| 499 | +def validate_img_count(count: int, expr: str) -> bool: |
| 500 | + """ |
| 501 | + Validate image count against multiple AND-ed conditions. |
| 502 | +
|
| 503 | + Example expressions: |
| 504 | + '>=10 <=200' |
| 505 | + '>5 !=13' |
| 506 | + '==42' |
| 507 | + """ |
| 508 | + conditions = expr.split(',') |
| 509 | + |
| 510 | + for cond in conditions: |
| 511 | + match = _COND_RE.fullmatch(cond.strip()) |
| 512 | + if not match: |
| 513 | + raise ValueError( |
| 514 | + f"Invalid imgCount condition '{cond}'. " |
| 515 | + "Valid examples: '>=10', '<=200', '!=13'" |
| 516 | + ) |
| 517 | + |
| 518 | + op_str, value = match.groups() |
| 519 | + value = int(value) |
| 520 | + |
| 521 | + if not OPS[op_str](count, value): |
| 522 | + return False |
| 523 | + |
| 524 | + return True |
| 525 | + |
478 | 526 | def check_setup_and_map(inputdir, outputdir, options): |
479 | 527 | """ |
480 | 528 | Check the input file space |
481 | 529 | If textInspect option is specified, accurately zip both the mappers |
482 | 530 | to yield a single mapper |
483 | 531 | """ |
484 | 532 | dcm_mapper = PathMapper.file_mapper(inputdir, outputdir, glob=f"**/*.{options.fileFilter}", fail_if_empty=False) |
| 533 | + count = len(dcm_mapper) |
485 | 534 |
|
486 | 535 | # Exit if minimum image count is not met |
487 | | - if len(dcm_mapper) < options.minImgCount: |
488 | | - print( |
489 | | - f"Total no. of images found ({len(dcm_mapper)}) is less than specified ({options.minImgCount}). Exiting analysis..") |
490 | | - sys.exit() |
| 536 | + try: |
| 537 | + if not validate_img_count(count, options.imgCount): |
| 538 | + print( |
| 539 | + f"Total no. of images found ({count}) does not satisfy " |
| 540 | + f"specified conditions ({options.imgCount}). " |
| 541 | + "Exiting analysis.." |
| 542 | + ) |
| 543 | + sys.exit(1) |
| 544 | + |
| 545 | + except ValueError as e: |
| 546 | + print(f"Argument error: {e}") |
| 547 | + sys.exit(2) |
491 | 548 | print(f"Total no. of images found: {len(dcm_mapper)}") |
492 | 549 |
|
493 | 550 | text_mapper = PathMapper.file_mapper(inputdir, outputdir, glob=f"**/*.{options.textFilter}", fail_if_empty=False) |
|
0 commit comments