diff --git a/monailabel/datastore/local.py b/monailabel/datastore/local.py index 2b3350025..3412853e0 100644 --- a/monailabel/datastore/local.py +++ b/monailabel/datastore/local.py @@ -218,6 +218,15 @@ def set_description(self, description: str): self._update_datastore_file() def _to_id(self, file: str) -> Tuple[str, str]: + """ + Derive an identifier and extension from a filename using the configured file extensions. + + Parameters: + file (str): Filename to parse. + + Returns: + Tuple[str, str]: The identifier and matched extension. + """ ext = file_ext(file) extensions = [e.replace("*", "") for e in self._extensions] for e in extensions: @@ -227,11 +236,28 @@ def _to_id(self, file: str) -> Tuple[str, str]: return id, ext def _to_label_id(self, file: str) -> Tuple[str, str]: + """Convert a label filename to its identifier and extension. + + Parameters: + file (str): Label filename to parse. + + Returns: + Tuple[str, str]: The label identifier and file extension. + """ if file.lower().endswith(".seg.nrrd"): return file[: -len(".seg.nrrd")], ".seg.nrrd" return self._to_id(file) def _filename(self, id: str, ext: str) -> str: + """Construct a filename by concatenating an identifier with its extension. + + Parameters: + id (str): The file identifier. + ext (str): The file extension. + + Returns: + str: The resulting filename. + """ return id + ext def _to_bytes(self, file): @@ -633,6 +659,15 @@ def _add_non_existing_images(self) -> int: return invalidate def _add_non_existing_labels(self, tag) -> int: + """ + Add label metadata for label files that exist on disk but are missing from the datastore. + + Parameters: + tag (str): Label tag identifying the directory containing the label files. + + Returns: + int: Number of labels added to the datastore. + """ invalidate = 0 self._init_from_datastore_file() diff --git a/monailabel/endpoints/datastore_review.py b/monailabel/endpoints/datastore_review.py index 696463dfe..5edce123d 100644 --- a/monailabel/endpoints/datastore_review.py +++ b/monailabel/endpoints/datastore_review.py @@ -38,6 +38,16 @@ def _safe_label_info(datastore, image_id: str, tag: str) -> Dict[str, Any]: + """ + Retrieve label metadata for an image and tag. + + Parameters: + image_id (str): Identifier of the image. + tag (str): Label tag used to locate the metadata. + + Returns: + Dict[str, Any]: The label metadata when it is a dictionary; otherwise, an empty dictionary. + """ try: info = datastore.get_label_info(image_id, tag) return info if isinstance(info, dict) else {} @@ -49,6 +59,18 @@ def _safe_label_info(datastore, image_id: str, tag: str) -> Dict[str, Any]: def _parse_date_range(date_range: Optional[str]) -> Optional[Tuple[datetime, datetime]]: + """ + Parse an optional comma-separated ISO timestamp range. + + Parameters: + date_range (Optional[str]): Range in the form ``start,end``. + + Returns: + Optional[Tuple[datetime, datetime]]: The parsed start and end timestamps, or ``None`` when no range is provided. + + Raises: + HTTPException: If the range is incomplete or contains invalid ISO timestamps. + """ if not date_range: return None @@ -63,6 +85,16 @@ def _parse_date_range(date_range: Optional[str]) -> Optional[Tuple[datetime, dat def _matches_date_range(value: Optional[str], parsed: Optional[Tuple[datetime, datetime]]) -> bool: + """ + Determine whether a timestamp falls within an optional inclusive date range. + + Parameters: + value (Optional[str]): ISO-formatted timestamp to evaluate. + parsed (Optional[Tuple[datetime, datetime]]): Inclusive start and end timestamps, or `None` to disable filtering. + + Returns: + bool: `true` if filtering is disabled or the timestamp falls within the range, `false` otherwise. + """ if not parsed: return True if not value: @@ -78,6 +110,17 @@ def _matches_date_range(value: Optional[str], parsed: Optional[Tuple[datetime, d def _review_case(datastore, image_id: str, tag: str) -> Dict[str, Any]: + """ + Build a review record containing image metadata, review details, and label status. + + Parameters: + datastore: Datastore used to retrieve image, label, and review information. + image_id (str): Identifier of the image. + tag (str): Label tag associated with the review. + + Returns: + Dict[str, Any]: Review record with derived status, reviewer information, and label presence. + """ image_info = datastore.get_image_info(image_id) or {} label_info = _safe_label_info(datastore, image_id, tag) labels = datastore.get_labels_by_image_id(image_id) or {} @@ -108,6 +151,22 @@ def _list_review_cases( date_range: Optional[str], tag: str, ) -> List[Dict[str, Any]]: + """ + Collect review cases that match the specified status, image, reviewer, date, and label tag filters. + + Parameters: + status_filter (Optional[str]): Review status to match. + search (Optional[str]): Comma-separated image IDs to include. + reviewer (Optional[str]): Reviewer name to match. + date_range (Optional[str]): ISO timestamp range in the format ``start,end``. + tag (str): Label tag used to build each review case. + + Returns: + List[Dict[str, Any]]: Review cases that satisfy all provided filters. + + Raises: + HTTPException: If ``date_range`` is not a valid ISO timestamp range. + """ datastore = app_instance().datastore() image_ids = datastore.list_images() parsed_range = _parse_date_range(date_range) @@ -134,6 +193,15 @@ def _list_review_cases( def _summary(items: List[Dict[str, Any]]) -> Dict[str, int]: + """ + Count review cases by overall total and status. + + Parameters: + items (List[Dict[str, Any]]): Review case records containing a status field. + + Returns: + Dict[str, int]: Counts for total, approved, flagged, pending, and unlabeled cases. + """ return { "total": len(items), "approved": sum(1 for item in items if item.get("status") == "approved"), @@ -144,6 +212,15 @@ def _summary(items: List[Dict[str, Any]]) -> Dict[str, int]: def _report_stats(items: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Summarize review cases by status and difficulty level. + + Parameters: + items (List[Dict[str, Any]]): Review case records to summarize. + + Returns: + Dict[str, Any]: Status and difficulty counts with the current recording timestamp. + """ return { **_summary(items), "easy": sum(1 for item in items if item.get("level") == "easy"), @@ -154,14 +231,15 @@ def _report_stats(items: List[Dict[str, Any]]) -> Dict[str, Any]: def _render_csv(items: List[Dict[str, Any]]) -> str: - def sanitize_csv_value(value: Any) -> str: - """Sanitize CSV values to prevent formula injection.""" - text = str(value) if value else "" - # Prefix values that start with formula-like characters - if text and text[0] in ("=", "+", "-", "@"): - return "'" + text - return text + """ + Render review case records as CSV text. + + Parameters: + items (List[Dict[str, Any]]): Review case records to include in the CSV output. + Returns: + str: CSV text containing a header row and one row for each review case. + """ handle = io.StringIO() writer = csv.writer(handle) writer.writerow(["image_id", "status", "level", "reviewer", "comment", "last_reviewed", "tag"]) @@ -181,9 +259,27 @@ def sanitize_csv_value(value: Any) -> str: def _render_html(stats: Dict[str, Any]) -> str: + """ + Render review statistics as an HTML report. + + Parameters: + stats (Dict[str, Any]): Statistics and generation timestamp to include in the report. + + Returns: + str: An HTML document containing review status and difficulty counts with percentages. + """ total = stats["total"] or 1 def row(name: str, value: int) -> str: + """Render an HTML table row containing a label, count, and percentage of the total. + + Parameters: + name (str): The label displayed in the row. + value (int): The count used to calculate the percentage. + + Returns: + str: An escaped HTML table row with the label, count, and percentage. + """ return f"{escape(name)}{value}" f"{(100.0 * value / total):.1f}%" return ( @@ -215,6 +311,21 @@ async def api_review_cases( tag: str = DefaultLabelTag.FINAL.value, user: User = Depends(RBAC(settings.MONAI_LABEL_AUTH_ROLE_USER)), ): + """ + List filtered review cases with pagination and aggregate status counts. + + Parameters: + offset (int): Number of matching cases to skip. + limit (int): Maximum number of cases to include in the results. + status_filter (Optional[str]): Status used to filter cases. + search (Optional[str]): Comma-separated image identifiers used to filter cases. + reviewer (Optional[str]): Reviewer name used to filter cases. + date_range (Optional[str]): ISO timestamp range in the format ``start,end``. + tag (str): Label tag used to build the review cases. + + Returns: + Dict[str, Any]: A response containing summary counts, paginated results, and filter metadata. + """ items = _list_review_cases(status_filter, search, reviewer, date_range, tag) results = items[offset : offset + limit] @@ -236,6 +347,18 @@ async def api_review_versions( image: str, user: User = Depends(RBAC(settings.MONAI_LABEL_AUTH_ROLE_USER)), ): + """ + Retrieve the available label versions for an image. + + Parameters: + image (str): Identifier of the image whose label versions are requested. + + Returns: + dict: A success response containing the image identifier and version metadata. + + Raises: + HTTPException: If no labels are found for the image. + """ datastore = app_instance().datastore() labels = datastore.get_labels_by_image_id(image) if not labels: @@ -266,6 +389,22 @@ async def api_review_report( tag: str = DefaultLabelTag.FINAL.value, user: User = Depends(RBAC(settings.MONAI_LABEL_AUTH_ROLE_USER)), ): + """ + Generate a review report in JSON, CSV, or HTML format. + + Parameters: + fmt (str): Output format: `"json"`, `"csv"`, or `"html"`. + reviewer (Optional[str]): Limits results to a specific reviewer. + date_range (Optional[str]): ISO timestamp range in `start,end` format. + tag (str): Label tag used to select review cases. + + Returns: + dict: A formatted report containing review statistics and, for JSON output, + the applied filters and matching review cases. + + Raises: + HTTPException: If `fmt` is not `"json"`, `"csv"`, or `"html"`. + """ items = _list_review_cases(None, None, reviewer, date_range, tag) stats = _report_stats(items) fmt = fmt.lower() diff --git a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py index 109662c38..d93d581f0 100644 --- a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py +++ b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py @@ -426,6 +426,9 @@ def cleanCache(self): # Section: Server def loadServerSelection(self): + """ + Populate the server URL selector with normalized addresses stored in application settings. + """ settings = qt.QSettings() serverUrlHistory = settings.value("MONAILabel/serverUrlHistory") @@ -437,14 +440,25 @@ def loadServerSelection(self): self.ui.comboBox_server_url.addItems(server_urls) def normalizeServerUrl(self, serverUrl: str) -> str: + """ + Normalize a server URL for consistent use by removing surrounding whitespace and trailing slashes. + + Parameters: + serverUrl (str): The server URL to normalize. + + Returns: + str: The normalized server URL, or an empty string when no URL is provided. + """ if not serverUrl: return "" return serverUrl.strip().rstrip("/") def init_dicom_stream(self): """ - initiates connection to monai server - Default: client listens on "http://127.0.0.1:8000" + Connect to the configured MONAI Label server and initialize the reviewer interface. + + The selected server URL is normalized before connection. Displays a warning and leaves + the interface uninitialized when the connection fails. """ # Check Connection self.cleanCache() @@ -1497,6 +1511,11 @@ def updateAfterEditingSegmentation(self): self.setVersionTagInComboBox(versionTag=newLabelNameCreated) def reloadImageAfterEditingLabel(self): + """ + Reload the current image using its latest label version after an edit. + + The image display and label version selector are refreshed to reflect the updated segmentation. + """ imageId = self.currentImageData.getFileName() latestVersion = self.currentImageData.getLatestVersionTag() logging.info(f"{self.getCurrentTime()}: Loading image (id='{imageId}') with version tag = '{latestVersion}'") @@ -1504,6 +1523,11 @@ def reloadImageAfterEditingLabel(self): self.fillComboBoxLabelVersions(self.currentImageData) def processDataStoreRecords(self): + """ + Process datastore records and display a warning when processing fails. + + The selected server address is normalized for inclusion in failure messages. + """ serverUrl: str = self.normalizeServerUrl(self.ui.comboBox_server_url.currentText) result: bool = self.logic.initMetaDataProcessing() if result is False: @@ -1668,11 +1692,11 @@ def updateLabelInfo( def loadDicomAndSegmentation(self, imageData: ImageData, tag: str): """ - Loads original Dicom image and Segmentation into slicer window + Load an image and, when available, its segmentation into the Slicer scene. + Parameters: - imageData (ImageData): Contains meta data (of dicom and segmenation) - which is required for rest request to monai server - in order to get dicom and segmenation (.nrrd). + imageData (ImageData): Image metadata used to retrieve the DICOM image and segmentation. + tag (str): Version tag identifying the segmentation to load. """ # Request dicom image_name = imageData.getFileName() @@ -1727,11 +1751,25 @@ def getPathToStore(self, segmentationFileName: str, tempDirectory: str) -> str: def displaySegmention(self, destination: str): """ - Displays the segmentation in slicer window + Display a segmentation file in the Slicer window. + + Parameters: + destination (str): Path to the segmentation file. """ segmentation = slicer.util.loadSegmentation(destination) def requestDicomImage(self, image_id: str, image_name: str, node_name: str): + """ + Download an image from the MONAI Label server and load it into Slicer. + + Parameters: + image_id (str): Identifier of the image to download. + image_name (str): File name used for temporary storage. + node_name (str): Name associated with the image node. + + Raises: + RuntimeError: If the server does not return the image. + """ response = self.imageDataController.requestImage(image_id) if response is None: raise RuntimeError(f"Failed to download image '{image_id}' from MONAI Label server") @@ -1745,7 +1783,9 @@ def requestDicomImage(self, image_id: str, image_name: str, node_name: str): def setTempFolderDir(self): """ - Create temporary dirctory to store the downloaded segmentation (.nrrd) + Create the temporary directory used to store downloaded segmentation files. + + The directory is created only once and its path is logged. """ if self.temp_dir is None: self.temp_dir = tempfile.TemporaryDirectory() diff --git a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/ImageDataController.py b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/ImageDataController.py index 670beb303..71f799712 100644 --- a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/ImageDataController.py +++ b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/ImageDataController.py @@ -235,9 +235,26 @@ def reuqestSegmentation(self, image_id: str, tag: str) -> requests.models.Respon return img_blob def getDicomDownloadUri(self, image_id: str) -> str: + """Return the download URI for an image's DICOM data. + + Parameters: + image_id (str): Identifier of the image. + + Returns: + str: DICOM download URI. + """ return self.monaiServerREST.getDicomDownloadUri(image_id) def requestImage(self, image_id: str) -> requests.models.Response: + """ + Request an image from the MONAI server. + + Parameters: + image_id (str): Identifier of the image to request. + + Returns: + requests.models.Response: The image response, or `None` if the request did not return data. + """ img_blob = self.monaiServerREST.requestImage(image_id) if img_blob is not None: logging.info( @@ -248,6 +265,15 @@ def requestImage(self, image_id: str) -> requests.models.Response: return img_blob def saveLabelInMonaiServer(self, image_in: str, label_in: str, tag: str, params: Dict): + """ + Save a label and its metadata to the MONAI server. + + Parameters: + image_in (str): Identifier or path of the source image. + label_in (str): Identifier or path of the label. + tag (str): Version tag for the label. + params (Dict): Label metadata and update parameters. + """ self.monaiServerREST.saveLabel(image_in, label_in, tag, params) def deleteLabelByVersionTag(self, imageId: str, versionTag: str) -> bool: diff --git a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/MonaiServerREST.py b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/MonaiServerREST.py index 42c3ceb01..c4dbbb0dc 100644 --- a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/MonaiServerREST.py +++ b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewerLib/MonaiServerREST.py @@ -25,10 +25,21 @@ class MonaiServerREST: def __init__(self, serverUrl: str): + """ + Initialize the REST client with a normalized server URL. + + Parameters: + serverUrl (str): Base URL of the MONAI server. Trailing slashes are removed when provided. + """ self.PARAMS_PREFIX_REST_REQUEST = "params" self.serverUrl = serverUrl.rstrip("/") if serverUrl else serverUrl def getServerUrl(self) -> str: + """Return the configured MONAI server URL. + + Returns: + str: The server URL. + """ return self.serverUrl def getCurrentTime(self) -> datetime: @@ -53,11 +64,28 @@ def requestDataStoreInfo(self) -> dict: return response.json() def getDicomDownloadUri(self, image_id: str) -> str: + """Build the download URI for a DICOM image. + + Parameters: + image_id (str): Identifier of the image to retrieve. + + Returns: + str: The encoded DICOM image download URI. + """ download_uri = f"{self.serverUrl}/datastore/image?image={quote_plus(image_id)}" logging.info(f"{self.getCurrentTime()}: REST: request dicom image '{download_uri}'") return download_uri def requestImage(self, image_id: str) -> requests.models.Response: + """ + Request an image from the MONAI server. + + Parameters: + image_id (str): Identifier of the image to request. + + Returns: + requests.models.Response: The successful image response, or None if the request fails or returns a non-200 status. + """ download_uri = self.getDicomDownloadUri(image_id) try: @@ -81,6 +109,16 @@ def requestImage(self, image_id: str) -> requests.models.Response: return response def requestSegmentation(self, image_id: str, tag: str) -> requests.models.Response: + """ + Request a segmentation for an image using the specified version tag. + + Parameters: + image_id (str): Identifier of the image whose segmentation is requested. + tag (str): Segmentation version tag; an empty string uses the ``final`` tag. + + Returns: + requests.models.Response: The successful HTTP response, or ``None`` if the request fails or returns a non-200 status code. + """ if tag == "": tag = "final" download_uri = f"{self.serverUrl}/datastore/label?label={quote_plus(image_id)}&tag={quote_plus(tag)}" diff --git a/sample-apps/reviewer/app.py b/sample-apps/reviewer/app.py index 9757fcd1f..89ef049a4 100644 --- a/sample-apps/reviewer/app.py +++ b/sample-apps/reviewer/app.py @@ -65,7 +65,17 @@ def __init__(self, app_dir: str, studies: str, conf: Dict): logger.info(f"Studies: {self.review_config['studies']}") def _load_review_config(self, studies: str, conf: Dict) -> Dict[str, Any]: - """Load review-specific configuration.""" + """ + Load review configuration from supplied values and environment variables. + + Parameters: + studies (str): Path or identifier for the studies datastore. + conf (Dict): Optional configuration values. + + Returns: + Dict[str, Any]: Resolved review configuration, including server, datastore, + reviewer, and mode settings. + """ conf_dict = conf or {} config = { @@ -86,29 +96,46 @@ def _load_review_config(self, studies: str, conf: Dict) -> Dict[str, Any]: return config def init_infers(self) -> Dict[str, Any]: + """Indicate that this application provides no inference components. + + Returns: + Dict[str, Any]: An empty dictionary. + """ return {} def init_trainers(self) -> Dict[str, Any]: + """ + Indicate that the application has no training components. + + Returns: + Dict[str, Any]: An empty dictionary. + """ return {} def init_strategies(self) -> Dict[str, Any]: + """ + Indicate that the application provides no strategy components. + + Returns: + Dict[str, Any]: An empty dictionary. + """ return {} def init_scoring_methods(self) -> Dict[str, Any]: + """Indicate that no scoring methods are configured. + + Returns: + Dict[str, Any]: An empty dictionary. + """ return {} def info(self) -> Dict[str, Any]: """ - Get application information. + Provide application metadata and review workflow capabilities. Returns: - { - "name": "MONAILabel Reviewer", - "description": "Lightweight review app", - "version": "1.0.0", - "studies": "/path/to/images", - "config": {...} - } + Dict[str, Any]: Application metadata including the resolved studies path, + review configuration, supported features, and review-only mode. """ meta = super().info() meta.update( diff --git a/sample-apps/reviewer/client.py b/sample-apps/reviewer/client.py index 1cca648b7..9a4190d64 100644 --- a/sample-apps/reviewer/client.py +++ b/sample-apps/reviewer/client.py @@ -48,11 +48,11 @@ class LightweightReviewClient: def __init__(self, server_url: Optional[str] = None, timeout: int = 30): """ - Initialize lightweight review client. + Initialize a review client for the specified MONAI Label server. Parameters: - - server_url: MONAI Label server URL - - timeout: Request timeout in seconds + server_url (str): MONAI Label server URL. + timeout (int): Request timeout in seconds. """ resolved_server_url = server_url or "http://localhost:8000" self.server_url = resolved_server_url.rstrip("/") @@ -65,7 +65,12 @@ def __init__(self, server_url: Optional[str] = None, timeout: int = 30): logger.info(f"ReviewClient initialized: {self.server_url}") def ping(self) -> bool: - """Check if server is reachable.""" + """ + Check whether the server responds successfully. + + Returns: + bool: `True` if the server responds with HTTP status 200, `False` otherwise. + """ try: response = requests.get(f"{self.server_url}", timeout=5) status = response.status_code == 200 @@ -80,24 +85,15 @@ def ping(self) -> bool: def list_images(self, offset: int = 0, limit: int = 100, status_filter: Optional[str] = None) -> Dict[str, Any]: """ - List all images available for review. + List reviewable images with optional pagination and status filtering. - Query Parameters: - - offset: Pagination offset (default: 0) - - limit: Pagination limit (default: 100) - - status_filter: Filter by status (approved/flagged/unapproved) + Parameters: + offset (int): Number of images to skip. + limit (int): Maximum number of images to return. + status_filter (Optional[str]): Status by which to filter images. Returns: - { - "summary": { - "total": 100, - "approved": 45, - "flagged": 5, - "pending": 50 - }, - "results": [...list of image metadata...], - "metadata": {...} - } + Dict[str, Any]: Review case data on success, or an error dictionary if the request fails. """ try: params = {"limit": str(limit), "offset": str(offset)} @@ -122,13 +118,13 @@ def list_images(self, offset: int = 0, limit: int = 100, status_filter: Optional def download_image(self, image_id: str) -> bytes: """ - Download DICOM image data. + Download the image data identified by `image_id`. Parameters: - - image_id: Unique identifier for the image + image_id (str): Unique identifier of the image. Returns: - Image file bytes in original format + bytes: Image data in its original format, or `None` if the download fails. """ try: response = requests.get( @@ -149,11 +145,11 @@ def download_image(self, image_id: str) -> bytes: def download_label(self, label_id: str, tag: str = "final") -> Optional[bytes]: """ - Download segmentation label or mask. + Download a segmentation label for the specified version. Parameters: - - label_id: Label/segmentation ID - - tag: Label version/tag (default: final) + label_id (str): Identifier of the label to download. + tag (str): Version or tag of the label. Returns: Binary label file bytes in original format @@ -176,17 +172,13 @@ def download_label(self, label_id: str, tag: str = "final") -> Optional[bytes]: def download_labelinfo(self, label_id: str) -> Dict[str, Any]: """ - Download label metadata. + Download metadata for a label using its final version. Parameters: - - label_id: Label/segmentation ID + label_id (str): Identifier of the label whose metadata to retrieve. Returns: - { - "status": "success", - "label_id": "label_001", - "info": {...metadata...} - } + Dict[str, Any] | None: The label metadata, or None if the request fails. """ try: response = requests.get( @@ -217,19 +209,19 @@ def update_labelinfo( workflow_id: Optional[str] = None, ) -> bool: """ - Update label metadata for review. + Update review metadata for a label. Parameters: - - label_id: Label/segmentation ID - - status: "approved" | "flagged" | "unapproved" - - level: "easy" | "medium" | "hard" - - comment: Review comment text - - reviewer_name: Name of reviewer - - reviewer_email: Email of reviewer - - workflow_id: Optional workflow identifier + label_id (str): Label or segmentation identifier. + status (str): Review status to assign. + level (Optional[str]): Review difficulty level. + comment (Optional[str]): Review comment. + reviewer_name (Optional[str]): Name of the reviewer. + reviewer_email (Optional[str]): Email address of the reviewer. + workflow_id (Optional[str]): Workflow identifier. Returns: - True on success, False on failure + bool: True if the metadata update succeeds; False otherwise. """ try: review_info = {"status": status, "reviewer_name": reviewer_name, "workflow_id": workflow_id} @@ -274,18 +266,18 @@ def save_label( version_note: Optional[str] = None, ) -> bool: """ - Save a new or updated segmentation label. + Upload a segmentation label for an image. Parameters: - - image_id: Image ID - - label_file: Path to segmentation file - - tag: Label version/tag - - reviewer_name: Name of reviewer/saver - - comment: Optional comment about this version - - version_note: Reason for this version + image_id (str): Identifier of the image associated with the label. + label_file (Path): Path to the label file to upload. + tag (str): Version tag for the label. + reviewer_name (str): Name of the reviewer submitting the label. + comment (Optional[str]): Comment associated with the label. + version_note (Optional[str]): Note describing the label version. Returns: - True on success, False on failure + bool: True if the label is saved successfully, False otherwise. """ try: # Prepare approvals data @@ -322,17 +314,13 @@ def save_label( def get_versions(self, image_id: str) -> Dict[str, Any]: """ - List all available versions for an image. + List the available label versions for an image. Parameters: - - image_id: Image ID + image_id (str): Identifier of the image whose label versions to retrieve. Returns: - { - "status": "success", - "image_id": "CT_abdomen_001", - "versions": [...] - } + Dict[str, Any]: Version information on success, or an error dictionary if the request fails. """ try: response = requests.get( @@ -353,14 +341,14 @@ def get_versions(self, image_id: str) -> Dict[str, Any]: def generate_report(self, fmt: str = "json", reviewer: str = None) -> Dict[str, Any]: """ - Generate a review summary report. + Generate a review summary report, optionally filtered by reviewer. Parameters: - - fmt: Report format ("json", "csv", "html") - - reviewer: Optional reviewer filter + fmt (str): Requested report format, such as "json", "csv", or "html". + reviewer (str): Optional reviewer name used to filter the report. Returns: - Report data + Dict[str, Any]: Report data on success, or an error dictionary when the request fails. """ try: response = requests.get( diff --git a/sample-apps/reviewer/lib/config.py b/sample-apps/reviewer/lib/config.py index 8fa98271e..4a0098e0c 100644 --- a/sample-apps/reviewer/lib/config.py +++ b/sample-apps/reviewer/lib/config.py @@ -33,15 +33,14 @@ def __init__( mode: str = "review", ): """ - Initialize reviewer configuration. + Initialize reviewer configuration from arguments and environment variables. Parameters: - - server_url: MONAI Label server URL (e.g., http://localhost:8000) - If None, runs in standalone mode using local cache - - cache_dir: Directory for cache storage - - reviewer_name: Name of the reviewer - - reviewer_email: Email of the reviewer - - mode: Operating mode - "review" (requires server) or "standalone" (local cache only) + server_url (Optional[str]): URL of the MONAI Label server. + cache_dir (Optional[str]): Directory used for review data caching. + reviewer_name (Optional[str]): Display name of the reviewer. + reviewer_email (Optional[str]): Email address of the reviewer. + mode (str): Operating mode, such as ``"review"`` or ``"standalone"``. """ # Resolve server URL self.server_url = server_url @@ -92,14 +91,25 @@ def anaconda_channel(self) -> str: @property def server_mode(self) -> str: - """Return the server mode description.""" + """Describe whether the reviewer operates in standalone or server-synchronized mode. + + Returns: + str: The current review mode description. + """ if self.review_only: return "Lightweight Review Mode (Standalone)" else: return "Full Review Mode with Server Sync" def dict(self) -> dict: - """Return configuration as dictionary.""" + """ + Export the review configuration and its derived operating settings. + + Returns: + dict: Configuration values including server details, reviewer identity, + review mode, workspace settings, history and cache limits, server mode, + and synchronization status. + """ return { "server_url": self.server_url, "cache_dir": self.cache_dir,