Repository: Project-MONAI/MONAILabel (language: python)

Issue:
Add MONAILabelReviewer Slicer Extension
Introduces an additional Slicer extension called MONAILabelReviewer as discussed in #722. This tool allows for quality control, scoring and easy browsing and filtering of annotated datasets and easy search for specific cases. The goal of MONAILabelReviewer is to become a comprehensive review tool that helps in the creation of very high-quality datasets through MONAILabel.

This PR will close #722.




Discussion / hints:
@SachidanandAlle @diazandr3s: 

We have started our PR on the MONAILabelReviewer in accordance to our discussion last week (28.April 2022). In our first step, we submitted the required changes in MONAILabel for persisting meta data of reviewed segmentations.

Would you like to pre-review on these initial commits?
During the next days, we will commit the source code of MONAILabelReviewer, as well.
I am ok to merge it to main branch.. the only breaking change to existing functionality is additional of new endpoint to update the labelinfo.. and it's good.. have no issues.. it's actually non breaking change..

Will wait for @lassoan to approve on the plugin part..
Specially on the plugin build.. now you have 2 plugins to build as part of Slicer build workflows.. I am not sure if the changes related to CMakeLists are good enough to take care of those.. @lassoan can confirm..
I am very sorry. 
I have just recognised that I caused an issue regarding the git commit history.
I forgot to sign off my last commit. Which is why I applied the following instructions according the description:
'git rebase HEAD~20 --signoff'
'push --force-with-lease origin add-monailabel-reviewer-extension'
@SachidanandAlle As a maintainer, are you able to fix up the conflicts that were introduced by pushing to this branch?
Things look very messy.. I suggest create a new branch (based on current main).. and almost all are new files added for SlicerReviewer.. (copy them to then new branch) and update the only few files (new endpoint added.. any other changes you have done for existing ones)

Please ignore DCO.. if it fails. I can approve it manually.. to avoid DCO error, when do git commit just use `-s` option.  It's ok if you have forgot to one ore two.. I can do manual approve in that case..
> Specially on the plugin build.. now you have 2 plugins to build as part of Slicer build workflows.. I am not sure if the changes related to CMakeLists are good enough to take care of those..

I can test the building of the Slicer extension and can provide results when the PR is cleaned up to have the appropriate commits.

Fix patch:
diff --git a/monailabel/endpoints/datastore.py b/monailabel/endpoints/datastore.py
index 6f9e0c9d3..8f07d1d72 100644
--- a/monailabel/endpoints/datastore.py
+++ b/monailabel/endpoints/datastore.py
@@ -183,3 +183,15 @@ async def api_download_image(image: str):
 @router.get("/label", summary="Download Label")
 async def api_download_label(label: str, tag: str):
     return download_label(label, tag)
+
+
+@router.put("/updatelabelinfo", summary="Update label info")
+async def api_update_label_info(label: str, params: str = Form("{}")):
+    return update_label_info(label, params)
+
+
+def update_label_info(label_id: str, params: str = Form("{}")):
+    save_params: Dict[str, Any] = json.loads(params) if params else {}
+    instance: MONAILabelApp = app_instance()
+    instance.datastore().update_label_info(label_id=label_id, label_tag="final", info=save_params)
+    return {}
diff --git a/plugins/slicer/MONAILabelReviewer/CMakeLists.txt b/plugins/slicer/MONAILabelReviewer/CMakeLists.txt
new file mode 100644
index 000000000..1cc49e42b
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/CMakeLists.txt
@@ -0,0 +1,31 @@
+#-----------------------------------------------------------------------------
+set(MODULE_NAME MONAILabelReviewer)
+
+#-----------------------------------------------------------------------------
+set(MODULE_PYTHON_SCRIPTS
+  ${MODULE_NAME}.py
+  )
+
+set(MODULE_PYTHON_RESOURCES
+  Resources/Icons/${MODULE_NAME}.png
+  Resources/UI/${MODULE_NAME}.ui
+  )
+
+#-----------------------------------------------------------------------------
+slicerMacroBuildScriptedModule(
+  NAME ${MODULE_NAME}
+  SCRIPTS ${MODULE_PYTHON_SCRIPTS}
+  RESOURCES ${MODULE_PYTHON_RESOURCES}
+  WITH_GENERIC_TESTS
+  )
+
+#-----------------------------------------------------------------------------
+if(BUILD_TESTING)
+
+  # Register the unittest subclass in the main script as a ctest.
+  # Note that the test will also be available at runtime.
+  slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py)
+
+  # Additional build-time testing
+  add_subdirectory(Testing)
+endif()
diff --git a/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py
new file mode 100644
index 000000000..c8baa1a34
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/MONAILabelReviewer.py
@@ -0,0 +1,1069 @@
+import datetime
+import logging
+import re
+from typing import Dict, List
+
+import qt
+import slicer
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.MONAILabelReviewerEnum import Level, SegStatus
+from ReviewerLibs.MONAILabelReviewerLogic import MONAILabelReviewerLogic
+from slicer.ScriptedLoadableModule import *
+from slicer.util import VTKObservationMixin
+
+
+class MONAILabelReviewer(ScriptedLoadableModule):
+    """Uses ScriptedLoadableModule base class, available at:
+    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+    """
+
+    def __init__(self, parent):
+        ScriptedLoadableModule.__init__(self, parent)
+        self.parent.title = "MONAILabel Reviewer"
+        self.parent.categories = ["Active Learning"]
+        self.parent.dependencies = []
+        self.parent.contributors = ["Minh Duc, Do (rAIdiance)"]
+        self.parent.helpText = """
+This module provides the user to review on segmentations on X-Ray-dicom images.
+See more information in <a href="...">module documentation</a>.
+"""
+        self.parent.acknowledgementText = """
+Developed by rAiDiance, and  funded by Berlin Institute of Health (BIH).
+"""
+
+
+class MONAILabelReviewerWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
+    """Uses ScriptedLoadableModuleWidget base class, available at:
+    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+    """
+
+    def __init__(self, parent=None):
+        """
+        Called when the user opens the module the first time and the widget is initialized.
+        """
+        ScriptedLoadableModuleWidget.__init__(self, parent)
+        VTKObservationMixin.__init__(self)  # needed for parameter node observation
+        self.logic = None
+        self._parameterNode = None
+        self._updatingGUIFromParameterNode = False
+
+        self.STATUS = SegStatus()
+        self.LEVEL = Level()
+
+        self.selectedReviewer: str = ""
+        self.selectedClientId: str = ""
+        self.listImageData: List[ImageData] = None
+        self.imageCounter: int = 0
+        self.currentImageData: ImageData = None
+        self.idToimageData: Dict[str, ImageData] = None
+
+        # Meta Information
+        self.finalStatus: str = ""
+        self.finalLevel: str = ""
+        self.finalComment: str = ""
+
+        self.reviewersModeIsActive = False
+
+        self.mapFiltersToBool: Dict[str, bool] = {
+            "segmented": False,
+            "notSegemented": False,
+            "approved": False,
+            "flagged": False,
+        }
+
+    def setup(self):
+        """
+        Called when the user opens the module the first time and the widget is initialized.
+        """
+        ScriptedLoadableModuleWidget.setup(self)
+
+        # Load widget from .ui file (created by Qt Designer).
+        # Additional widgets can be instantiated manually and added to self.layout.
+        uiWidget = slicer.util.loadUI(self.resourcePath("UI/MONAILabelReviewer.ui"))
+        self.layout.addWidget(uiWidget)
+        self.ui = slicer.util.childWidgetVariables(uiWidget)
+
+        self.dialogWidget = slicer.util.loadUI(self.resourcePath("UI/DialogInfo.ui"))
+        # Set scene in MRML widgets. Make sure that in Qt designer the top-level qMRMLWidget's
+        # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's.
+        # "setMRMLScene(vtkMRMLScene*)" slot.
+        uiWidget.setMRMLScene(slicer.mrmlScene)
+
+        # Create logic class. Logic implements all computations that should be possible to run
+        # in batch mode, without a graphical user interface.
+        self.logic = MONAILabelReviewerLogic()
+
+        self.setLightVersion()
+        self.segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
+        self.addSegmentator()
+        self.ui.verticalLayout_10.addWidget(self.segmentEditorWidget)
+        self.loadServerSelection()
+
+        #  Section: Widget Elements
+        self.ui.btn_connect_monai.clicked.connect(self.init_dicom_stream)
+        self.ui.btn_load.clicked.connect(self.loadImageData)
+
+        self.ui.btn_approved.clicked.connect(self.approveSegmentation)
+        self.ui.btn_mark_revision.clicked.connect(self.flagSegmentation)
+
+        self.ui.btn_next.clicked.connect(self.getNextSegmentation)
+        self.ui.btn_previous.clicked.connect(self.getPreviousSegmenation)
+
+        self.ui.btn_easy.clicked.connect(self.setEasy)
+        self.ui.btn_medium.clicked.connect(self.setMedium)
+        self.ui.btn_hard.clicked.connect(self.setHard)
+
+        self.ui.btn_search.clicked.connect(self.search)
+        self.ui.btn_search_annotator_reviewer.clicked.connect(self.searchByAnnotatorReviewer)
+        self.ui.btn_search_level.clicked.connect(self.searchByLevel)
+
+        self.ui.checkBox_search_approved.clicked.connect(self.checkedAppprovedSearch)
+        self.ui.checkBox_search_flagged.clicked.connect(self.checkedFlaggedSearch)
+
+        self.ui.btn_show_image.clicked.connect(self.showSearchedImage)
+
+        self.ui.checkBox_flagged.clicked.connect(self.checkedFlagged)
+        self.ui.checkBox_approved.clicked.connect(self.checkApproved)
+        self.ui.checkBox_not_segmented.clicked.connect(self.checkNotSegmented)
+        self.ui.checkBox_segmented.clicked.connect(self.checkSegmented)
+
+        self.ui.btn_basic_mode.clicked.connect(self.setLightVersion)
+        self.ui.btn_reviewers_mode.clicked.connect(self.setReviewerVersion)
+        self.dialogWidget.pushButton.clicked.connect(self.closeDialog)
+        self.ui.comboBox_clients.currentIndexChanged.connect(self.index_changed)
+        self.ui.comboBox_reviewers.currentIndexChanged.connect(self.indexReviewerchanged)
+
+    def getCurrentTime(self):
+        return datetime.datetime.now()
+
+    def cleanup(self):
+        """
+        Called when the application closes and the module widget is destroyed.
+        """
+        self.removeObservers()
+
+    def indexReviewerchanged(self, index):
+        logging.info(f"{self.getCurrentTime()}: Selected reviewer: '{self.ui.comboBox_reviewers.currentText}'")
+        self.selectedReviewer = self.ui.comboBox_reviewers.currentText
+
+    def index_changed(self, index):
+        self.loadImageData()
+
+    # Section: Dialog window
+    def displayDialogInfo(self, title: str, message: str):
+        self.dialogWidget.label_title.setText(title)
+        self.dialogWidget.label_message.setText(message)
+        self.dialogWidget.exec()
+
+    def closeDialog(self):
+        self.dialogWidget.close()
+
+    def setReviewerVersion(self):
+        self.reviewersModeIsActive = True
+        # section: Server
+        # Reviewer Field
+        self.ui.label_20.show()
+        self.ui.comboBox_reviewers.show()
+
+        # Approved bar
+        self.ui.label_17.show()
+        self.ui.progressBar_approved_total.show()
+        self.ui.label_idx_appr_image.show()
+
+        # section: Data set explorer
+
+        # Approved bar
+        self.ui.label_10.show()
+        self.ui.progressBar_approved_client.show()
+        self.ui.label_idx_appr_image_client.show()
+
+        # filter option
+        self.ui.label_6.show()
+        self.ui.checkBox_not_segmented.show()
+        self.ui.checkBox_flagged.show()
+        self.ui.checkBox_segmented.show()
+        self.ui.checkBox_approved.show()
+
+        # section: Data evaluation
+        self.ui.btn_easy.show()
+        self.ui.btn_medium.show()
+        self.ui.btn_hard.show()
+        self.ui.label_level_difficulty.show()
+        self.ui.btn_mark_revision.show()
+        self.ui.btn_approved.show()
+
+        # imag information
+        self.ui.label_14.show()
+        self.ui.lineEdit_status.show()
+        self.ui.label_16.show()
+        self.ui.lineEdit_level.show()
+        self.ui.plainText_comment.show()
+        if self.ui.btn_basic_mode.isChecked():
+            self.ui.btn_basic_mode.setChecked(False)
+
+        self.collapseAllSecions()
+
+    # Section:  Light version Option
+
+    def setLightVersion(self):
+        self.reviewersModeIsActive = False
+        # section: Server
+        # Reviewer Field
+        self.ui.label_20.hide()
+        self.ui.comboBox_reviewers.hide()
+
+        # Approved bar
+        self.ui.label_17.hide()
+        self.ui.progressBar_approved_total.hide()
+        self.ui.label_idx_appr_image.hide()
+
+        # section: Data set explorer
+
+        # Approved bar
+        self.ui.label_10.hide()
+        self.ui.progressBar_approved_client.hide()
+        self.ui.label_idx_appr_image_client.hide()
+
+        # filter option
+        self.ui.label_6.hide()
+        self.ui.checkBox_not_segmented.hide()
+        self.ui.checkBox_flagged.hide()
+        self.ui.checkBox_segmented.hide()
+        self.ui.checkBox_approved.hide()
+
+        # section: Data evaluation
+        self.ui.btn_easy.hide()
+        self.ui.btn_medium.hide()
+        self.ui.btn_hard.hide()
+        self.ui.label_level_difficulty.hide()
+        self.ui.btn_mark_revision.hide()
+        self.ui.btn_approved.hide()
+
+        # imag information
+        self.ui.label_14.hide()
+        self.ui.lineEdit_status.hide()
+        self.ui.label_16.hide()
+        self.ui.lineEdit_level.hide()
+        self.ui.plainText_comment.hide()
+        if self.ui.btn_reviewers_mode.isChecked():
+            self.ui.btn_reviewers_mode.setChecked(False)
+
+        self.collapseAllSecions()
+
+    def cleanCache(self):
+        self.logic = MONAILabelReviewerLogic()
+        self.selectedReviewer = ""
+        self.selectedClientId = ""
+        self.listImageData = None
+        self.imageCounter = 0
+        self.currentImageData = None
+        self.idToimageData = None
+
+        # Meta Information
+        self.finalStatus = ""
+        self.finalLevel = ""
+        self.finalComment = ""
+        logging.info(f"{self.getCurrentTime()}: Cache is cleaned")
+
+    # Section: Server
+    def loadServerSelection(self):
+        settings = qt.QSettings()
+        serverUrlHistory = settings.value("MONAILabel/serverUrlHistory")
+
+        self.ui.comboBox_server_url.clear()
+        self.ui.comboBox_server_url.addItems(serverUrlHistory.split(";"))
+
+    def init_dicom_stream(self):
+        """
+        initiates connection to monai server
+        Default: client listens on "http://127.0.0.1:8000"
+        """
+        # Check Connection
+        self.cleanCache()
+        serverUrl: str = self.ui.comboBox_server_url.currentText
+        isConnected: bool = self.logic.connectToMonaiServer(serverUrl)
+        if not isConnected:
+            warningMessage = f"Connection to server failed \ndue to invalid ip '{serverUrl}'"
+            self.displayDialogInfo(title="Warning", message=warningMessage)
+            return
+        result = self.logic.initMetaDataProcessing()
+        if result is False:
+            warningMessage = (
+                "Request for datastore-info failed.\nPlease check if server address is correct \n('{}')!".format(
+                    serverUrl
+                )
+            )
+            self.displayDialogInfo(title="Warning", message=warningMessage)
+            return
+        self.initUI()
+
+    def collapseAllSecions(self):
+        self.ui.collapsibleButton_search_image.enabled = False
+        self.ui.collapsibleButton_dicom_stream.enabled = False
+        self.ui.collapsibleButton_dicom_evaluation.enabled = False
+
+        self.ui.collapsibleButton_search_image.collapsed = True
+        self.ui.collapsibleButton_dicom_stream.collapsed = True
+        self.ui.collapsibleButton_dicom_evaluation.collapsed = True
+
+    def initUI(self):
+
+        self.selectedReviewer = self.ui.comboBox_reviewers.currentText
+        if self.reviewersModeIsActive and self.selectedReviewer == "":
+            warningMessage = "Missing reviewer's name.\nPlease enter your id or name in the reviewer's field!"
+            logging.warning(warningMessage)
+            self.displayDialogInfo(title="Warning", message=warningMessage)
+            return
+        self.ui.collapsibleButton_search_image.enabled = True
+        self.ui.collapsibleButton_dicom_stream.enabled = True
+
+        # set Segmentation progress bar
+        self.setProgessBar()
+
+        # fill combobox
+        self.fillComboBoxes()
+
+        # set up buttons
+        self.setButtons()
+
+        self.selectedClientId = ""
+
+    def setButtons(self):
+        self.ui.btn_approved.setCheckable(True)
+        self.ui.btn_mark_revision.setCheckable(True)
+        self.ui.btn_easy.setCheckable(True)
+        self.ui.btn_medium.setCheckable(True)
+        self.ui.btn_hard.setCheckable(True)
+        self.ui.btn_reviewers_mode.setCheckable(True)
+        self.ui.btn_basic_mode.setCheckable(True)
+        self.ui.btn_show_image.enabled = False
+
+    def setProgessBar(self):
+        progessBarData = self.logic.getStatistics()
+        self.ui.progressBar_segmentation.setProperty("value", progessBarData["segmentationProgress"])
+        self.ui.label_idx_seg_image.setText(progessBarData["idxTotalSegmented"])
+        self.ui.label_idx_appr_image.setText(progessBarData["idxTotalApproved"])
+        self.ui.progressBar_approved_total.setProperty("value", progessBarData["progressPercentage"])
+
+    def fillComboBoxes(self):
+        # clients
+        clientIds = self.logic.getClientIds()
+
+        self.ui.comboBox_clients.clear()
+        self.ui.comboBox_clients.addItem("All")
+        for clientId in clientIds:
+            self.ui.comboBox_clients.addItem(str(clientId))
+
+        # combobox in search section
+        self.ui.comboBox_search_annotator.clear()
+        self.ui.comboBox_search_annotator.addItem("All")
+        for clientId in clientIds:
+            self.ui.comboBox_search_annotator.addItem(str(clientId))
+
+        # reviewers
+        reviewers = self.logic.getReviewers()
+        self.ui.comboBox_reviewers.addItem(self.selectedReviewer)
+        for reviewer in reviewers:
+            if reviewer == self.selectedReviewer:
+                continue
+            self.ui.comboBox_reviewers.addItem(str(reviewer))
+
+        # combobox in search section
+        self.ui.comboBox_search_reviewer.addItem("All")
+        for reviewer in reviewers:
+            self.ui.comboBox_search_reviewer.addItem(str(reviewer))
+
+    def cleanDicomStreamSection(self):
+        self.finalStatus = ""
+        self.finalLevel = ""
+        self.finalComment = ""
+
+        self.selectedClientId = None
+        self.imageCounter = 0
+        self.currentImageData = None
+        self.idToimageData = None
+        self.listImageData = None
+
+        self.cleanProgressBarDicomStreamSection()
+        self.cleanCheckBoxes()
+        self.resetHorizontalSlider()
+
+    # Section: Loading images
+    def loadImageData(self):
+        if (self.selectedClientId == self.ui.comboBox_clients.currentText) and (self.isDifferentFilter() is False):
+            return
+        self.imageCounter = 0
+
+        self.cleanSearchSection()
+        # select segmentator: ALL
+        self.selectedClientId = self.ui.comboBox_clients.currentText
+        if self.selectedClientId == "All":
+            self.listImageData = self.loadImageDataWithFilter(selectedClientId="")
+            self.ui.checkBox_segmented.setEnabled(True)
+            self.ui.checkBox_not_segmented.setEnabled(True)
+            self.setProgressBarOfAll()
+
+        # select segmentator: client was selected
+        if self.selectedClientId != "All":
+            self.listImageData = self.loadImageDataWithFilter(selectedClientId=self.selectedClientId)
+            self.setCheckBoxesClient()
+            self.setProgressBarOfClient(self.selectedClientId)
+
+        logging.info(
+            "{}: Successfully loaded Image data [total = {}, category = '{}']".format(
+                self.getCurrentTime(), len(self.listImageData), self.selectedClientId
+            )
+        )
+
+        if len(self.listImageData) > 0:
+            self.currentImageData = self.listImageData[self.imageCounter]
+            self.loadNextImage(self.currentImageData)
+
+        self.ui.collapsibleButton_dicom_evaluation.enabled = True
+        self.ui.collapsibleButton_dicom_evaluation.collapsed = False
+        self.setHorizontalSlider(len(self.listImageData))
+        self.collectFilters()
+        self.setLoadButtonColor(reload=False)
+
+    def loadImageDataWithFilter(self, selectedClientId: str) -> list:
+        isApproved = bool(self.ui.checkBox_approved.isChecked())
+        isFlagged = bool(self.ui.checkBox_flagged.isChecked())
+        isNotSegmented = bool(self.ui.checkBox_not_segmented.isChecked())
+        segmented = bool(self.ui.checkBox_segmented.isChecked())
+        logging.info(
+            "{}: Selected filters: segmented= {} | isNotSegmented= {} | isApproved= {} | isFlagged= {}".format(
+                self.getCurrentTime(), segmented, isNotSegmented, isApproved, isFlagged
+            )
+        )
+        if selectedClientId == "":
+            return self.logic.getAllImageData(segmented, isNotSegmented, isApproved, isFlagged)
+        return self.logic.getImageDataByClientId(selectedClientId, isApproved, isFlagged)
+
+    def setProgressBarOfAll(self):
+        progessBarData = self.logic.getStatistics()
+        # Progress bar: Segmented/TotalImage
+        self.ui.progressBar_segmented_client.setProperty("value", progessBarData["segmentationProgressAllPercentage"])
+        self.ui.label_idx_seg_image_client.setText(progessBarData["idxTotalSegmented"])
+        # Progress bar: approvalCount/TotalImage
+        self.ui.progressBar_approved_client.setProperty("value", progessBarData["approvalProgressPercentage"])
+        self.ui.label_idx_appr_image_client.setText(progessBarData["idxTotalApproved"])
+
+    def cleanProgressBarDicomStreamSection(self):
+        self.ui.progressBar_segmented_client.setProperty("value", 0)
+        self.ui.progressBar_approved_client.setProperty("value", 0)
+        self.ui.label_idx_seg_image_client.setText("x/y")
+        self.ui.label_idx_appr_image_client.setText("x/y")
+
+    def setLoadButtonColor(self, reload: bool):
+        if reload:  # reload required
+            self.ui.btn_load.setStyleSheet("background-color : rgb(169, 169, 169)")
+            return
+        self.ui.btn_load.setStyleSheet("background-color : rgb(0, 144, 81)")
+
+    def setProgressBarOfClient(self, selectedClientId: str):
+        percentageApprovedOfClient, idxApprovedOfClient = self.logic.getPercentageApproved(selectedClientId)
+        self.ui.progressBar_approved_client.setProperty("value", percentageApprovedOfClient)
+        self.ui.label_idx_appr_image_client.setText(idxApprovedOfClient)
+
+        percentageSemgmentedByClient, idxSegmentedByClient = self.logic.getPercentageSemgmentedByClient(
+            selectedClientId
+        )
+        self.ui.progressBar_segmented_client.setProperty("value", percentageSemgmentedByClient)
+        self.ui.label_idx_seg_image_client.setText(idxSegmentedByClient)
+
+    def setHorizontalSlider(self, loadesImageCount: int):
+        self.ui.horizontalSlider_image_idx.setMinimum(0)
+        self.ui.horizontalSlider_image_idx.setMaximum(loadesImageCount - 1)
+        idxImage = f"Image: {self.imageCounter + 1}/{len(self.listImageData)}"
+        self.ui.label_idx_image.setText(idxImage)
+
+    def updateHorizontalSlider(self):
+        self.ui.horizontalSlider_image_idx.setValue(self.imageCounter)
+        idxImage = f"Image: {self.imageCounter + 1}/{len(self.listImageData)}"
+        self.ui.label_idx_image.setText(idxImage)
+
+    def resetHorizontalSlider(self):
+        self.ui.horizontalSlider_image_idx.setValue(1)
+        self.ui.label_idx_image.setText("Image:")
+
+    # Section: Filter
+    def collectFilters(self):
+        self.mapFiltersToBool["segmented"] = self.ui.checkBox_segmented.isChecked()
+        self.mapFiltersToBool["notSegemented"] = self.ui.checkBox_not_segmented.isChecked()
+        self.mapFiltersToBool["approved"] = self.ui.checkBox_approved.isChecked()
+        self.mapFiltersToBool["flagged"] = self.ui.checkBox_flagged.isChecked()
+
+    def isDifferentFilter(self) -> bool:
+        if self.mapFiltersToBool["segmented"] != self.ui.checkBox_segmented.isChecked():
+            return True
+        if self.mapFiltersToBool["notSegemented"] != self.ui.checkBox_not_segmented.isChecked():
+            return True
+        if self.mapFiltersToBool["approved"] != self.ui.checkBox_approved.isChecked():
+            return True
+        if self.mapFiltersToBool["flagged"] != self.ui.checkBox_flagged.isChecked():
+            return True
+        return False
+
+    # CheckBox: clean
+    def cleanCheckBoxes(self):
+        self.ui.checkBox_segmented.setChecked(False)
+        self.ui.checkBox_not_segmented.setChecked(False)
+        self.ui.checkBox_flagged.setChecked(False)
+        self.ui.checkBox_approved.setChecked(False)
+
+    # CheckBox: flagged
+    def setCheckBoxesClient(self):
+        self.setLoadButtonColor(reload=True)
+        self.ui.checkBox_not_segmented.setEnabled(False)
+        self.ui.checkBox_segmented.setChecked(True)
+        self.ui.checkBox_segmented.setEnabled(False)
+
+    # CheckBox: flagged
+    def checkedFlagged(self):
+        self.setLoadButtonColor(reload=True)
+        self.ui.checkBox_segmented.setChecked(True)
+        if self.ui.checkBox_approved.isChecked():
+            self.ui.checkBox_approved.setChecked(False)
+        if self.ui.checkBox_not_segmented.isChecked():
+            self.ui.checkBox_not_segmented.setChecked(False)
+
+    # CheckBox: approved
+    def checkApproved(self):
+        self.setLoadButtonColor(reload=True)
+        self.ui.checkBox_segmented.setChecked(True)
+        if self.ui.checkBox_flagged.isChecked():
+            self.ui.checkBox_flagged.setChecked(False)
+        if self.ui.checkBox_not_segmented.isChecked():
+            self.ui.checkBox_not_segmented.setChecked(False)
+
+    # CheckBox: NOT segmented
+    def checkNotSegmented(self):
+        self.setLoadButtonColor(reload=True)
+        if self.ui.checkBox_approved.isChecked():
+            self.ui.checkBox_approved.setChecked(False)
+        if self.ui.checkBox_flagged.isChecked():
+            self.ui.checkBox_flagged.setChecked(False)
+        if self.ui.checkBox_segmented.isChecked():
+            self.ui.checkBox_segmented.setChecked(False)
+
+    # CheckBox: segmented
+    def checkSegmented(self):
+        self.setLoadButtonColor(reload=True)
+        if self.ui.checkBox_segmented.isChecked() is False:
+            self.ui.checkBox_approved.setChecked(False)
+            self.ui.checkBox_flagged.setChecked(False)
+            return
+
+        if self.ui.checkBox_not_segmented.isChecked():
+            self.ui.checkBox_not_segmented.setChecked(False)
+
+    # Section: Search Image
+    def cleanSearchSection(self):
+        self.ui.tableWidge_imageMeta.setRowCount(0)
+        self.ui.tableWidge_imageMeta.clearContents()
+        self.ui.textEdit_search.clear()
+
+    def search(self):
+        """
+        After triggering search button, load images and segmentation by input ids
+        """
+        self.cleanDicomStreamSection()
+
+        if self.ui.textEdit_search.toPlainText() == "":
+            logging.info(f"{self.getCurrentTime()}: Search input field is empty")
+            return
+
+        idsStr = self.ui.textEdit_search.toPlainText()
+        idList = self.getIdsFromString(idsStr)
+
+        self.idToimageData = self.logic.getMultImageDataByIds(idList)
+        self.listImageData = [*self.idToimageData.values()]
+
+        foundIdList = [imageData.getName() for imageData in self.listImageData]
+        notFoundIdList = [id for id in idList if (id not in foundIdList)]
+        self.loadSearchImageMetaInTable(self.listImageData, notFoundIdList)
+
+        self.ui.collapsibleButton_dicom_evaluation.enabled = True
+        self.setHorizontalSlider(len(foundIdList))
+        if len(foundIdList) > 0:
+            self.setSearchResultMessage(numOfFound=len(foundIdList))
+            self.loadFirstImage()
+        else:
+            self.setSearchResultMessage(numOfFound=0)
+
+    def searchByAnnotatorReviewer(self):
+        selectedAnnotator: str = self.ui.comboBox_search_annotator.currentText
+        selectedReviewer: str = self.ui.comboBox_search_reviewer.currentText
+        isApproved: bool = bool(self.ui.checkBox_search_approved.isChecked())
+        isFlagged: bool = bool(self.ui.checkBox_search_flagged.isChecked())
+
+        self.idToimageData = self.logic.searchByAnnotatorReviewer(
+            selectedAnnotator, selectedReviewer, isApproved, isFlagged
+        )
+        self.listImageData = [*self.idToimageData.values()]
+
+        self.loadSearchImageMetaInTable(self.listImageData, [])
+        if len(self.listImageData) > 0:
+            self.ui.collapsibleButton_dicom_evaluation.enabled = True
+            self.setSearchResultMessage(numOfFound=len(self.idToimageData))
+            self.setHorizontalSlider(len(self.idToimageData))
+            self.loadFirstImage()
+        else:
+            self.setSearchResultMessage(numOfFound=0)
+
+    def searchByLevel(self):
+        isEasy: bool = bool(self.ui.checkBox_search_easy.isChecked())
+        isMedium: bool = bool(self.ui.checkBox_search_medium.isChecked())
+        isHard: bool = bool(self.ui.checkBox_search_hard.isChecked())
+
+        self.idToimageData = self.logic.searchByLevel(isEasy, isMedium, isHard)
+        self.listImageData = [*self.idToimageData.values()]
+
+        self.loadSearchImageMetaInTable(self.listImageData, [])
+        if len(self.listImageData) > 0:
+            self.ui.collapsibleButton_dicom_evaluation.enabled = True
+            self.setSearchResultMessage(numOfFound=len(self.idToimageData))
+            self.setHorizontalSlider(len(self.idToimageData))
+            self.loadFirstImage()
+        else:
+            self.setSearchResultMessage(numOfFound=0)
+
+    def setSearchResultMessage(self, numOfFound: int):
+        if numOfFound == 0:
+            self.ui.label_search_result.setText("Result: No images found.")
+            self.ui.label_search_result.setStyleSheet("color: red")
+        else:
+            resultMessage = f"Result: {numOfFound} images found."
+            self.ui.label_search_result.setText(resultMessage)
+            self.ui.label_search_result.setStyleSheet("color: green")
+
+    def checkedAppprovedSearch(self):
+        isFlagged: bool = bool(self.ui.checkBox_search_flagged.isChecked())
+        if isFlagged:
+            self.ui.checkBox_search_flagged.setChecked(False)
+
+    def checkedFlaggedSearch(self):
+        isApproved: bool = bool(self.ui.checkBox_search_approved.isChecked())
+        if isApproved:
+            self.ui.checkBox_search_approved.setChecked(False)
+
+    def loadSearchImageMetaInTable(self, foundlist: List[ImageData], notFoundIdList: List[str]):
+        """
+        Set table content after triggering button "show"
+        Parameters:
+            foundlist (list): list contains found ids
+            notFoundIdList (list): list contains not found ids
+        """
+        rowCount = len(foundlist) + len(notFoundIdList)
+        self.ui.tableWidge_imageMeta.setRowCount(rowCount)
+        rowCounter = 0
+        for row, imageData in enumerate(foundlist):
+            self.ui.tableWidge_imageMeta.setItem(row, 0, qt.QTableWidgetItem(imageData.getName()))
+            self.ui.tableWidge_imageMeta.setItem(row, 1, qt.QTableWidgetItem("Yes"))
+            self.ui.tableWidge_imageMeta.setItem(row, 2, qt.QTableWidgetItem(str(imageData.isSegemented())))
+            rowCounter += 1
+
+        for row, notFoundId in enumerate(notFoundIdList):
+            self.ui.tableWidge_imageMeta.setItem(rowCounter, 0, qt.QTableWidgetItem(notFoundId))
+            self.ui.tableWidge_imageMeta.setItem(rowCounter, 1, qt.QTableWidgetItem("No"))
+            self.ui.tableWidge_imageMeta.setItem(rowCounter, 2, qt.QTableWidgetItem("No"))
+            rowCounter += 1
+
+        self.ui.btn_show_image.enabled = True
+
+    def loadFirstImage(self):
+        self.imageCounter = 0
+        self.currentImageData = self.listImageData[self.imageCounter]
+        self.loadNextImage(self.currentImageData)
+        # self.imageCounter += 1
+        self.updateHorizontalSlider()
+
+    def showSearchedImage(self):
+        """
+        displays dicom & segmentation to corresponding selected row in listed ids
+        """
+        selectedRow = self.ui.tableWidge_imageMeta.currentRow()
+        if selectedRow == -1:
+            return
+        selectedImageId = self.ui.tableWidge_imageMeta.item(selectedRow, 0).text()
+
+        if selectedImageId not in self.idToimageData:
+            logging.info(f"{self.getCurrentTime()}: Selected image id [id = {selectedImageId}] was not found")
+            return
+        self.currentImageData = self.idToimageData[selectedImageId]
+        self.loadNextImage(self.currentImageData)
+
+    def removeAllWhiteSpaces(self, strChain) -> str:
+        """
+        removes white spaces within string
+        """
+        pattern = r"\s+"
+        return re.sub(pattern, "", strChain)
+
+    def getIdsFromString(self, idStr: str) -> List[str]:
+        """
+        parses string which contains comma seperated ids
+        Parameters:
+            idStr (str): string which contains comma seperated ids
+        Returns:
+            list: contains ids
+        """
+        cleanedStr = self.removeAllWhiteSpaces(idStr)
+        idsList = cleanedStr.split(",")
+        return list(dict.fromkeys(idsList))  # remove all duplicates
+
+    # Section: Dicom stream
+    # Button: Approve
+    def approveSegmentation(self):
+        statusApproved = self.ui.btn_approved.isChecked()
+        statusFlagged = self.ui.btn_mark_revision.isChecked()
+
+        if statusFlagged or self.finalStatus == self.STATUS.FLAGGED:
+            self.ui.btn_mark_revision.setChecked(False)
+            self.ui.btn_mark_revision.setDown(False)
+        if statusApproved:
+            self.finalStatus = self.STATUS.APPROVED
+            self.ui.btn_approved.setChecked(True)
+        else:
+            self.finalStatus = ""
+        self.updateDisplayImageMetaData()
+
+    # Button: Flagge
+    def flagSegmentation(self):
+        statusApproved = self.ui.btn_approved.isChecked()
+        statusFlagged = self.ui.btn_mark_revision.isChecked()
+
+        if statusApproved or self.finalStatus == self.STATUS.APPROVED:
+            self.ui.btn_approved.setChecked(False)
+            self.ui.btn_approved.setDown(False)
+        if statusFlagged:
+            self.finalStatus = self.STATUS.FLAGGED
+            self.ui.btn_mark_revision.setChecked(True)
+        else:
+            self.finalStatus = ""
+        self.updateDisplayImageMetaData()
+
+    # Button: Clear
+    def clearButtons(self):
+        self.ui.btn_mark_revision.setChecked(False)
+        self.ui.btn_approved.setChecked(False)
+
+        self.ui.btn_mark_revision.setDown(False)
+        self.ui.btn_approved.setDown(False)
+
+        self.ui.btn_easy.setChecked(False)
+        self.ui.btn_medium.setChecked(False)
+        self.ui.btn_hard.setChecked(False)
+
+        self.ui.btn_easy.setDown(False)
+        self.ui.btn_medium.setDown(False)
+        self.ui.btn_hard.setDown(False)
+
+    def disableButtons(self):
+        self.ui.btn_easy.setDown(False)
+        self.ui.btn_medium.setDown(False)
+        self.ui.btn_hard.setDown(False)
+
+    # Button: Easy
+    def setEasy(self):
+        levelEasy = self.ui.btn_easy.isChecked()
+        levelMedium = self.ui.btn_medium.isChecked()
+        levelHard = self.ui.btn_hard.isChecked()
+
+        if levelEasy:
+            self.finalLevel = self.LEVEL.EASY
+
+        if levelEasy is False and self.finalLevel == self.LEVEL.EASY:
+            self.finalLevel = ""
+
+        if levelMedium or levelHard or (self.finalLevel == self.LEVEL.HARD) or (self.finalLevel == self.LEVEL.MEDIUM):
+
+            self.ui.btn_medium.setChecked(False)
+            self.ui.btn_hard.setChecked(False)
+            self.ui.btn_medium.setDown(False)
+            self.ui.btn_hard.setDown(False)
+
+        self.updateDisplayImageMetaData()
+
+    # Button: Medium
+    def setMedium(self):
+        levelEasy = self.ui.btn_easy.isChecked()
+        levelMedium = self.ui.btn_medium.isChecked()
+        levelHard = self.ui.btn_hard.isChecked()
+
+        if levelMedium:
+            self.finalLevel = self.LEVEL.MEDIUM
+
+        if levelMedium is False and self.finalLevel == self.LEVEL.MEDIUM:
+            self.finalLevel = ""
+
+        if levelEasy or levelHard or (self.finalLevel == self.LEVEL.HARD) or (self.finalLevel == self.LEVEL.EASY):
+
+            self.ui.btn_easy.setChecked(False)
+            self.ui.btn_hard.setChecked(False)
+            self.ui.btn_easy.setDown(False)
+            self.ui.btn_hard.setDown(False)
+
+        self.updateDisplayImageMetaData()
+
+    # Button: Hard
+    def setHard(self):
+        levelEasy = self.ui.btn_easy.isChecked()
+        levelMedium = self.ui.btn_medium.isChecked()
+        levelHard = self.ui.btn_hard.isChecked()
+
+        if levelHard:
+            self.finalLevel = self.LEVEL.HARD
+
+        if levelHard is False and self.finalLevel == self.LEVEL.HARD:
+            self.finalLevel = ""
+
+        if levelEasy or levelMedium or (self.finalLevel == self.LEVEL.MEDIUM) or (self.finalLevel == self.LEVEL.EASY):
+
+            self.ui.btn_easy.setChecked(False)
+            self.ui.btn_medium.setChecked(False)
+            self.ui.btn_easy.setDown(False)
+            self.ui.btn_medium.setDown(False)
+
+        self.updateDisplayImageMetaData()
+
+    # Button: Next
+    def getNextSegmentation(self):
+        """
+        after triggering next button:
+          1. persist meta data in monai server
+          2. update progess bar
+          3. load next dicom & segmentation
+        """
+
+        # Persist MetaData
+        self.persistMetaInMonaiServer()
+
+        # Request Next Image
+        self.imageCounter += 1
+
+        if self.imageCounter >= len(self.listImageData):
+            message = f"{self.getCurrentTime()}: End of list has been reached."
+            logging.warning(message)
+            self.displayDialogInfo(title="Warning", message=message)
+            self.imageCounter = len(self.listImageData) - 1
+            return
+        self.updateHorizontalSlider()
+        self.currentImageData = self.listImageData[self.imageCounter]
+
+        # Displays Next Image
+        self.loadNextImage(self.currentImageData)
+
+    # Monai Server: Put
+    def persistMetaInMonaiServer(self):
+        """
+        Sends the updated meta data of dicom and segmentation to monai-server
+        Monai-server incorporates that information into datastore.json file
+        """
+        updatedMetaJson = self.updateImageData()
+        imageId = self.currentImageData.getName()
+        logging.info("updatedMetaJson")
+        logging.info(updatedMetaJson)
+        if updatedMetaJson == "":
+            return
+        self.logic.updateLabelInfo(imageId, updatedMetaJson)
+
+    def updateImageData(self) -> str:
+        """
+        update meta data in information box
+        Returns:
+          jsonStr (str): json dictionary which is transformed string
+                         contains updated meta data
+        """
+        self.finalComment = self.ui.plainText_comment.toPlainText()
+
+        logging.info("Status:  " + self.finalLevel)
+        if self.currentImageData.isEqualSegmentationMeta(
+            status=self.finalStatus, level=self.finalLevel, approvedBy=self.selectedReviewer, comment=self.finalComment
+        ):
+            logging.info(f"{self.getCurrentTime()}: No changes for image (id='{self.currentImageData.getName()}')")
+            return ""
+
+        self.currentImageData.updateSegmentationMeta(
+            status=self.finalStatus, level=self.finalLevel, approvedBy=self.selectedReviewer, comment=self.finalComment
+        )
+        jsonStr = self.currentImageData.getMeta()
+
+        if jsonStr is None:
+            logging.info(f"{self.getCurrentTime()}: No update for Image (id='{self.currentImageData.getName()}')")
+            return ""
+        logging.info(f"{self.getCurrentTime()}: Successfully updated Image (id='{self.currentImageData.getName()}')")
+        return jsonStr
+
+    # Button: Previouse
+    def getPreviousSegmenation(self):
+        """
+        Loads the previous dicom and corresponding segmentation
+        after useres tiggers Previous-Button
+        """
+        self.imageCounter -= 1
+        if self.imageCounter < 0:
+            message = f"{self.getCurrentTime()}: Lower limit of data set has been reached."
+            logging.warning(message)
+            self.displayDialogInfo(title="Warning", message=message)
+            self.imageCounter = 0
+            return
+        self.updateHorizontalSlider()
+        self.currentImageData = self.listImageData[self.imageCounter]
+        self.loadNextImage(self.currentImageData)
+
+    def clearImageData(self):
+        self.ui.lineEdit_image_id.setText("")
+        self.ui.lineEdit_status.setText("")
+        self.ui.lineEdit_segmentator.setText("")
+        self.ui.lineEdit_level.setText("")
+        self.ui.lineEdit_date.setText("")
+        self.ui.plainText_comment.setPlainText("")
+
+    def displayImageMetaData(self, imageData):
+        """
+        Displays meta info of dicom and segmentation in the info box on slicer
+
+        Parameters:
+          imageData (ImageData): Contains meta data (of dicom and segmenation)
+        """
+        self.clearImageData()
+        self.clearButtons()
+
+        self.ui.lineEdit_image_id.setText(imageData.getName())
+        self.ui.lineEdit_segmentator.setText(imageData.getClientId())
+        self.ui.lineEdit_date.setText(imageData.getTime())
+
+        self.ui.lineEdit_status.setText(imageData.getStatus())
+        self.ui.plainText_comment.setPlainText(imageData.getComment())
+
+        if imageData.isApproved():
+            self.finalStatus = self.STATUS.APPROVED
+            self.ui.btn_approved.setChecked(True)
+            self.ui.btn_approved.setDown(True)
+
+        if imageData.isFlagged():
+            self.finalStatus = self.STATUS.FLAGGED
+            self.ui.btn_mark_revision.setChecked(True)
+            self.ui.btn_mark_revision.setDown(True)
+
+        if imageData.getLevel() != "":
+            self.finalLevel = imageData.getLevel()
+            if self.finalLevel == self.LEVEL.EASY:
+                self.ui.btn_easy.setDown(True)
+                self.ui.btn_easy.setChecked(True)
+                self.setEasy()
+            if self.finalLevel == self.LEVEL.MEDIUM:
+                self.ui.btn_medium.setDown(True)
+                self.ui.btn_medium.setChecked(True)
+                self.setMedium()
+            if self.finalLevel == self.LEVEL.HARD:
+                self.ui.btn_hard.setDown(True)
+                self.ui.btn_hard.setChecked(True)
+                self.setHard()
+            self.ui.lineEdit_level.setText(imageData.getLevel())
+
+    def updateDisplayImageMetaData(self):
+        """
+        Displays updated level (easy, medium, hard)
+        in the info box on slicer
+        """
+        self.ui.lineEdit_status.setText(self.finalStatus)
+        self.ui.lineEdit_level.setText(self.finalLevel)
+
+    def loadNextImage(self, imageData):
+        """
+        Loads original Dicom image and Segmentation into slicer window
+        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).
+        """
+        slicer.mrmlScene.Clear()
+        self.finalStatus = ""
+        self.finalLevel = ""
+        self.finalComment = ""
+
+        self.clearButtons()
+        self.displayImageMetaData(imageData)
+        self.logic.loadDicomAndSegmentation(imageData)
+
+        if imageData.getStatus() != self.STATUS.NOT_SEGMENTED:
+            self.displayLabelOfSegmentation()
+
+    # Section: Display label
+    def addSegmentator(self):
+        self.segmentEditorWidget.setMasterVolumeNodeSelectorVisible(False)
+        self.segmentEditorWidget.setSegmentationNodeSelectorVisible(False)
+        self.segmentEditorWidget.setSwitchToSegmentationsButtonVisible(False)
+
+        self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
+        self.segmentEditorWidget.unorderedEffectsVisible = False
+        self.segmentEditorWidget.setReadOnly(True)
+        self.segmentEditorWidget.setEffectNameOrder([])
+
+    def displayLabelOfSegmentation(self):
+        self.selectParameterNode()
+        self.getDefaultMasterVolumeNodeID()
+        self.segmentEditorWidget.SegmentationNodeComboBox.setCurrentNodeIndex(0)
+        self.segmentEditorWidget.MasterVolumeNodeComboBox.setCurrentNodeIndex(0)
+
+    def selectParameterNode(self):
+        # Select parameter set node if one is found in the scene, and create one otherwise
+        segmentEditorSingletonTag = "SegmentEditor"
+        segmentEditorNode = slicer.mrmlScene.GetSingletonNode(segmentEditorSingletonTag, "vtkMRMLSegmentEditorNode")
+        if segmentEditorNode is None:
+            segmentEditorNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLSegmentEditorNode")
+            segmentEditorNode.UnRegister(None)
+            segmentEditorNode.SetSingletonTag(segmentEditorSingletonTag)
+            segmentEditorNode = slicer.mrmlScene.AddNode(segmentEditorNode)
+        self.segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
+
+    def getDefaultMasterVolumeNodeID(self):
+        layoutManager = slicer.app.layoutManager()
+        firstForegroundVolumeID = None
+        # Use first background volume node in any of the displayed layouts.
+        # If no beackground volume node is in any slice view then use the first
+        # foreground volume node.
+        for sliceViewName in layoutManager.sliceViewNames():
+            sliceWidget = layoutManager.sliceWidget(sliceViewName)
+            if not sliceWidget:
+                continue
+            compositeNode = sliceWidget.mrmlSliceCompositeNode()
+            if compositeNode.GetBackgroundVolumeID():
+                return compositeNode.GetBackgroundVolumeID()
+            if compositeNode.GetForegroundVolumeID() and not firstForegroundVolumeID:
+                firstForegroundVolumeID = compositeNode.GetForegroundVolumeID()
+        # No background volume was found, so use the foreground volume (if any was found)
+        return firstForegroundVolumeID
+
+
+class MONAILabelReviewerTest(ScriptedLoadableModuleTest):
+    """
+    This is the test case for your scripted module.
+    Uses ScriptedLoadableModuleTest base class, available at:
+    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+    """
+
+    def setUp(self):
+        """Do whatever is needed to reset the state - typically a scene clear will be enough."""
+        slicer.mrmlScene.Clear()
+
+    def runTest(self):
+        """Run as few or as many tests as needed here."""
+        self.setUp()
+        self.test_MONAILabelReviewer1()
+
+    def test_MONAILabelReviewer1(self):
+        """Ideally you should have several levels of tests.  At the lowest level
+        tests should exercise the functionality of the logic with different inputs
+        (both valid and invalid).  At higher levels your tests should emulate the
+        way the user would interact with your code and confirm that it still works
+        the way you intended.
+        One of the most important features of the tests is that it should alert other
+        developers when their changes will have an impact on the behavior of your
+        module.  For example, if a developer removes a feature that you depend on,
+        your test should break so they know that the feature is needed.
+        """
+
+        self.delayDisplay("Starting the test")
diff --git a/plugins/slicer/MONAILabelReviewer/README.md b/plugins/slicer/MONAILabelReviewer/README.md
new file mode 100644
index 000000000..dffed2d23
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/README.md
@@ -0,0 +1,75 @@
+# Purpose
+Radiologists have different levels of experience reading X-ray images.
+Therefore, agreement of several radiologists on X-ray segmentations (especially in difficult cases) is required to increase the overall quality of a data set, which is then used to model a neural network.
+MONAILABELReviewer is a tool for research groups to check the quality of the segmentation of their data sets.
+
+![distributedWork](https://user-images.githubusercontent.com/30056369/158844144-94769304-cd4c-4630-ac6a-3dc124a9fc22.png)
+
+
+# Import MONAILABELReviewer into 3D Slicer
+1. Select "Edit"
+2. Select "Application Settings"
+3. Select "Modules"
+4. Select "Add"
+5. Within Browser select folder "MONAILabelReviewer"
+
+![ImportReviewerIntoSlicer](https://user-images.githubusercontent.com/30056369/158845199-1f723b8b-a64e-4bdc-8596-974e952569d9.png)
+
+# MonaiLabelReviewer UI
+MonaiLabelReviewer has two usage modes, "Reviewer Mode" and "Basic Mode". The latter can be enabled by checking the checkbox next to "Basic Mode".
+"Reviewer Mode" provides advanced features such as filer options and segmentation classification by difficulty (see subsection "UI in Reviewer Mode" for further description)
+
+# UI in basic mode
+1. If checkbox is selected, "basic mode" is activated (just for streaming through the segmentations)
+2. Progress bar displays how many images have already been segmented in total.
+3. Progress bar displays how many images have already been segmented by the selected annotator
+4. Combobox: Selection of annotator (if option "All" is selected, the dataset includes segmentations of all annotators)
+5. Slide bar: Displays currently index of image within the selected dataset
+6. Lines which displays the meta data: imageId, annotator's name, date
+7. Segmentation selection box: Hide/Show-option of segmentation layers
+
+![UiBasicMode](https://user-images.githubusercontent.com/30056369/158844598-cd6a0ea9-2e2f-4da6-b2e7-7900c8e00b83.png)
+
+
+#Required extensions in MonaiLabel
+
+In order to persist the information created by MonaiLabelReviewer during the review process, an additional rest endpoint needs to be introduced into MonaiLabel.
+In particular, the following methods (see below) need to be added in the datastore.py file.
+(We will apply these changes in the monai community, so the workflow in MonaiLabelReviewer will be available without any additional manual changes in MonaiLabel.)
+
+├── MONAILAIBEL
+    ├── monailabel
+        ├── endpoints
+            ├── datastore.py
+
+```
+@router.put("/updatelabelinfo", summary="Update label info")
+async def api_update_label_info(image: str, params: str = Form("{}")):
+    return update_label_info(image, params)
+
+def update_label_info(id: str, params: str = Form("{}")):
+  save_params: Dict[str, Any] = json.loads(params) if params else {}
+  instance: MONAILabelApp = app_instance()
+  instance.datastore().update_image_info(id, save_params)
+  return {}
+```
+
+# UI in Reviewer mode
+1. If checkbox is not selected, "Reviewer Mode" is activated (the enables all feature for reviewing the segmentations)
+2. Selection of reviewer's name or add new reviewer
+3. Progress bar displays how many images have already been approved in total.
+4. Progress bar displays how many images of selected Annotator have already been approved
+5. Buttons (Easy, Medium, Hard) allows reviewer to classify the difficulty of segmentation to Easy, Medium, Hard
+6. "Approve" Button: After the reviewer approves the segmentation, it can be included in the neural network modelling dataset
+7. "Flag" Button allows the reviewer to mark a segmentation for later evaluation by another radiologist
+8. Comment field: Reviewer can add comment into that box regarding the segmentation. If commented review is flagged additionally, the annotator can improve the segmentation according to comment
+9. Filter options allows the reviewer to select a subset of image data set (not segmented, segmented, flagged, approved)
+
+![UiReviewerMode](https://user-images.githubusercontent.com/30056369/158844810-27848c54-29d5-4d74-b1f2-27e38e92b150.png)
+
+
+# Search  by Image Id
+After entering a list of comma-separated image IDs in the left field, the right field displays a list of IDs of the corresponding found images.
+That data set can be reviewed using the "Next"-"Previous"-Button.
+
+![MonaiLabelReviewer SearchField](https://user-images.githubusercontent.com/30056369/159154537-0f97f004-0c61-4b63-947b-b7b55a3e61b1.png)
\ No newline at end of file
diff --git a/plugins/slicer/MONAILabelReviewer/Resources/Icons/MONAILabelReviewer.png b/plugins/slicer/MONAILabelReviewer/Resources/Icons/MONAILabelReviewer.png
new file mode 100644
index 000000000..5d83ab4f0
Binary files /dev/null and b/plugins/slicer/MONAILabelReviewer/Resources/Icons/MONAILabelReviewer.png differ
diff --git a/plugins/slicer/MONAILabelReviewer/Resources/UI/DialogInfo.ui b/plugins/slicer/MONAILabelReviewer/Resources/UI/DialogInfo.ui
new file mode 100644
index 000000000..d51ce46f8
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/Resources/UI/DialogInfo.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>393</width>
+    <height>184</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="1" column="0" colspan="2">
+    <widget class="QLabel" name="label_message">
+     <property name="text">
+      <string>Message</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QPushButton" name="pushButton">
+     <property name="text">
+      <string>Ok</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" colspan="2">
+    <widget class="QLabel" name="label_title">
+     <property name="layoutDirection">
+      <enum>Qt::LeftToRight</enum>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">font: 18pt &quot;.SF NS Text&quot;;</string>
+     </property>
+     <property name="text">
+      <string>Title</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/slicer/MONAILabelReviewer/Resources/UI/MONAILabelReviewer.ui b/plugins/slicer/MONAILabelReviewer/Resources/UI/MONAILabelReviewer.ui
new file mode 100644
index 000000000..f6a74adb9
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/Resources/UI/MONAILabelReviewer.ui
@@ -0,0 +1,803 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MONAILabelReviewer</class>
+ <widget class="qMRMLWidget" name="MONAILabelReviewer">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>555</width>
+    <height>855</height>
+   </rect>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="6" column="0">
+    <widget class="ctkCollapsibleButton" name="collapsibleButton_dicom_stream">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="text">
+      <string>Data Set Explorer</string>
+     </property>
+     <property name="collapsed">
+      <bool>true</bool>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_5">
+      <item row="0" column="0">
+       <layout class="QGridLayout" name="gridLayout_4">
+        <item row="0" column="2">
+         <widget class="QPushButton" name="btn_load">
+          <property name="text">
+           <string>Load</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="1">
+         <widget class="QProgressBar" name="progressBar_approved_client">
+          <property name="styleSheet">
+           <string notr="true">selection-background-color: rgba(255, 147, 0, 209);</string>
+          </property>
+          <property name="value">
+           <number>0</number>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QComboBox" name="comboBox_clients"/>
+        </item>
+        <item row="2" column="0">
+         <widget class="QLabel" name="label_10">
+          <property name="text">
+           <string>Approved</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="label_9">
+          <property name="text">
+           <string>Segmented</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="0">
+         <widget class="QLabel" name="label_7">
+          <property name="text">
+           <string>Annotator</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QProgressBar" name="progressBar_segmented_client">
+          <property name="styleSheet">
+           <string notr="true">selection-background-color: rgba(78, 157, 246, 209);</string>
+          </property>
+          <property name="value">
+           <number>0</number>
+          </property>
+          <property name="textVisible">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QLabel" name="label_6">
+          <property name="text">
+           <string>Filter</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <layout class="QHBoxLayout" name="horizontalLayout_6">
+          <item>
+           <layout class="QVBoxLayout" name="verticalLayout_3">
+            <item>
+             <widget class="QCheckBox" name="checkBox_not_segmented">
+              <property name="enabled">
+               <bool>false</bool>
+              </property>
+              <property name="text">
+               <string>not segmented</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_flagged">
+              <property name="text">
+               <string>flagged</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <layout class="QVBoxLayout" name="verticalLayout_5">
+            <item>
+             <widget class="QCheckBox" name="checkBox_segmented">
+              <property name="text">
+               <string>segmented</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_approved">
+              <property name="text">
+               <string>approved</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </item>
+        <item row="1" column="2">
+         <widget class="QLabel" name="label_idx_seg_image_client">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="2">
+         <widget class="QLabel" name="label_idx_appr_image_client">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="7" column="0">
+    <widget class="ctkCollapsibleButton" name="collapsibleButton_dicom_evaluation">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="text">
+      <string>Data Evaluation</string>
+     </property>
+     <property name="collapsed">
+      <bool>true</bool>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QLabel" name="label_level_difficulty">
+        <property name="text">
+         <string>Level of difficulty</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_5">
+        <item>
+         <widget class="QPushButton" name="btn_easy">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgb(0, 250, 146);</string>
+          </property>
+          <property name="text">
+           <string>Easy</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_medium">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgba(255, 251, 0, 179);</string>
+          </property>
+          <property name="text">
+           <string>Medium</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_hard">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgba(255, 38, 0, 179);</string>
+          </property>
+          <property name="text">
+           <string>Hard</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_3">
+        <item>
+         <widget class="QPushButton" name="btn_previous">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgb(255, 147, 0);</string>
+          </property>
+          <property name="text">
+           <string>Previous</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_next">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgb(118, 214, 255);</string>
+          </property>
+          <property name="text">
+           <string>Next</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_mark_revision">
+          <property name="text">
+           <string>Flag</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_approved">
+          <property name="text">
+           <string>Approve</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <widget class="QLabel" name="label_idx_image">
+          <property name="text">
+           <string>Image: x/y</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QSlider" name="horizontalSlider_image_idx">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_10">
+        <item>
+         <widget class="QLabel" name="label_11">
+          <property name="text">
+           <string>Image information </string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QSplitter" name="splitter_2">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <widget class="QWidget" name="layoutWidget">
+           <layout class="QHBoxLayout" name="horizontalLayout_4">
+            <item>
+             <layout class="QVBoxLayout" name="verticalLayout_9">
+              <item>
+               <widget class="QLabel" name="label_12">
+                <property name="text">
+                 <string>Image id: </string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_14">
+                <property name="text">
+                 <string>Status:</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_13">
+                <property name="text">
+                 <string>Annotator:</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_16">
+                <property name="text">
+                 <string>Difficulty Level:</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_15">
+                <property name="text">
+                 <string>Date:</string>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <layout class="QVBoxLayout" name="verticalLayout_8">
+              <item>
+               <widget class="QLineEdit" name="lineEdit_image_id">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLineEdit" name="lineEdit_status">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLineEdit" name="lineEdit_segmentator">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLineEdit" name="lineEdit_level">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLineEdit" name="lineEdit_date">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QPlainTextEdit" name="plainText_comment">
+           <property name="placeholderText">
+            <string>Add Comment</string>
+           </property>
+          </widget>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_9">
+     <item>
+      <widget class="QPushButton" name="btn_basic_mode">
+       <property name="styleSheet">
+        <string notr="true">background-color: rgb(118, 214, 255);</string>
+       </property>
+       <property name="text">
+        <string>Basic mode</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btn_reviewers_mode">
+       <property name="styleSheet">
+        <string notr="true">background-color: rgb(255, 126, 121);</string>
+       </property>
+       <property name="text">
+        <string>Reviewer's mode</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="4" column="0">
+    <widget class="ctkCollapsibleButton" name="CollapsibleButton">
+     <property name="text">
+      <string>Server</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="0" column="0">
+       <layout class="QGridLayout" name="gridLayout_3">
+        <item row="1" column="0">
+         <widget class="QLabel" name="label_19">
+          <property name="text">
+           <string>Server IP:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QLabel" name="label_8">
+          <property name="text">
+           <string>Segmented</string>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="1">
+         <widget class="QProgressBar" name="progressBar_approved_total">
+          <property name="styleSheet">
+           <string notr="true">selection-background-color: rgb(255, 147, 0);</string>
+          </property>
+          <property name="value">
+           <number>0</number>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="2">
+         <widget class="QLabel" name="label_idx_appr_image">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="QProgressBar" name="progressBar_segmentation">
+          <property name="value">
+           <number>0</number>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QComboBox" name="comboBox_server_url">
+          <property name="editable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="2">
+         <widget class="QPushButton" name="btn_connect_monai">
+          <property name="styleSheet">
+           <string notr="true">background-color: rgba(0, 144, 81, 1);</string>
+          </property>
+          <property name="text">
+           <string>Connect</string>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="0">
+         <widget class="QLabel" name="label_17">
+          <property name="text">
+           <string>Approved</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="2">
+         <widget class="QLabel" name="label_idx_seg_image">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="0">
+         <widget class="QLabel" name="label_20">
+          <property name="text">
+           <string>Reviewer:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="1">
+         <widget class="QComboBox" name="comboBox_reviewers">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="editable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="8" column="0">
+    <widget class="ctkCollapsibleButton" name="collapsibleButton_search_image">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="text">
+      <string>Search Images</string>
+     </property>
+     <property name="collapsed">
+      <bool>true</bool>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_15">
+        <item>
+         <widget class="QTabWidget" name="tabWidget">
+          <property name="currentIndex">
+           <number>1</number>
+          </property>
+          <widget class="QWidget" name="ById">
+           <property name="minimumSize">
+            <size>
+             <width>252</width>
+             <height>0</height>
+            </size>
+           </property>
+           <attribute name="title">
+            <string>Ids</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_4">
+            <item>
+             <widget class="QLabel" name="label_18">
+              <property name="text">
+               <string>Image Ids</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QTextEdit" name="textEdit_search">
+              <property name="placeholderText">
+               <string>imageId_1, imageId2, ...</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="btn_search">
+              <property name="styleSheet">
+               <string notr="true">background-color: rgb(146, 146, 146);</string>
+              </property>
+              <property name="text">
+               <string>Search</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="tab_8">
+           <attribute name="title">
+            <string>Annotator/Reviewer</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_6">
+            <item>
+             <widget class="QLabel" name="label">
+              <property name="text">
+               <string>Select annotator</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="comboBox_search_annotator"/>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_3">
+              <property name="text">
+               <string>Select reviewer</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="comboBox_search_reviewer"/>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_search_approved">
+              <property name="text">
+               <string>approved</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_search_flagged">
+              <property name="text">
+               <string>flagged</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_2">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QPushButton" name="btn_search_annotator_reviewer">
+              <property name="styleSheet">
+               <string notr="true">background-color: rgb(146, 146, 146);</string>
+              </property>
+              <property name="text">
+               <string>Search</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="tab">
+           <attribute name="title">
+            <string>Quality</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_7">
+            <item>
+             <widget class="QLabel" name="label_2">
+              <property name="text">
+               <string>Select level of difficulty</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_3">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_search_easy">
+              <property name="text">
+               <string>easy</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_search_medium">
+              <property name="text">
+               <string>medium</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="checkBox_search_hard">
+              <property name="text">
+               <string>hard</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_4">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QPushButton" name="btn_search_level">
+              <property name="styleSheet">
+               <string notr="true">background-color: rgb(146, 146, 146);</string>
+              </property>
+              <property name="text">
+               <string>Search</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_16">
+        <item>
+         <widget class="QLabel" name="label_search_result">
+          <property name="text">
+           <string>Result:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QTableWidget" name="tableWidge_imageMeta">
+          <property name="sortingEnabled">
+           <bool>true</bool>
+          </property>
+          <column>
+           <property name="text">
+            <string>Image Id</string>
+           </property>
+           <property name="font">
+            <font>
+             <pointsize>10</pointsize>
+            </font>
+           </property>
+           <property name="textAlignment">
+            <set>AlignCenter</set>
+           </property>
+          </column>
+          <column>
+           <property name="text">
+            <string>found</string>
+           </property>
+           <property name="font">
+            <font>
+             <pointsize>10</pointsize>
+            </font>
+           </property>
+           <property name="textAlignment">
+            <set>AlignCenter</set>
+           </property>
+          </column>
+          <column>
+           <property name="text">
+            <string>segmented</string>
+           </property>
+          </column>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="btn_show_image">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>Show</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="9" column="0">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>ctkCollapsibleButton</class>
+   <extends>QWidget</extends>
+   <header>ctkCollapsibleButton.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>qMRMLWidget</class>
+   <extends>QWidget</extends>
+   <header>qMRMLWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/DataStoreKeys.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/DataStoreKeys.py
new file mode 100644
index 000000000..37e0dd6d0
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/DataStoreKeys.py
@@ -0,0 +1,72 @@
+from typing import List
+
+"""
+DataStoreKeys contains arrays which represent the structure of datastore.json
+That provides an overview of keys which are relevant for the access the key-value-pair
+within json file.
+Below please have a look on an example of such entry in datastore.json.
+"""
+
+
+class DataStoreKeys:
+    def __init__(self):
+
+        self.OBJECT = "objects"
+        self.FINAL = "final"
+        self.ORIGINAL = "original"
+        self.ANNOTATE = "annotate"
+        self.RANDOM = "Random"
+        self.INFO = "info"
+        self.LABEL_INFO = "label_info"
+
+        self.IMAGE_INFO = ["image", "info"]
+        self.FILENAME = ["image", "info", "name"]
+        self.NODE_NAME = ["image", "info", "name"]
+
+        self.CHECKSUM = ["image", "info", "checksum"]
+        self.TIMESTAMP = ["image", "info", "ts"]
+        self.TIMESTAMP_ANNOTATE = ["image", "info", "strategy", "annotate", "ts"]
+        self.TIMESTAMP_RANDOM = ["image", "info", "strategy", "Random", "ts"]
+
+        self.STRATEGY = ["image", "info", "strategy"]
+        self.CLIENT_ID_BY_ANNOTATE = ["image", "info", "strategy", "annotate", "client_id"]
+        self.CLIENT_ID_BY_RANDOM = ["image", "info", "strategy", "Random", "client_id"]
+        self.CLIENT_ID = ["labels", "final", "info", "client_id"]
+
+        self.LABELS = ["labels"]
+        self.LABELS_FINAL = ["labels", "final"]
+        self.LABELS_FINAL_INFO = ["labels", "final", "info"]
+        self.LABELS_INFO = ["labels", "original", "info", "label_info"]
+        self.LABELS_FINAL_INFO_LABELS_INFO = ["labels", "final", "info", "label_info"]
+        self.SEGMENTATION_NAME_BY_FINAL = ["labels", "final", "info", "name"]
+        self.SEGMENTATION_NAME_BY_ORIGINAL = ["labels", "original", "info", "name"]
+
+        # Additional entries in json file which contains meta data
+        self.META = "segmentationMeta"
+        self.META_STATUS = "status"
+        self.META_LEVEL = "level"
+        self.APPROVED_BY = "approvedBy"
+
+        self.META_EDIT_TIME = "editTime"
+        self.META_COMMENT = "comment"
+
+    def getMeta(self, key: str, label: str) -> List[str]:
+        return ["labels"] + [label] + ["info", "segmentationMeta"] + [key]
+
+    def getMetaStatus(self, label: str):
+        return self.getMeta(key=self.META_STATUS, label=label)
+
+    def getMetaLevel(self, label: str):
+        return self.getMeta(key=self.META_LEVEL, label=label)
+
+    def getMetaApprovedBy(self, label: str):
+        return self.getMeta(key=self.APPROVED_BY, label=label)
+
+    def getMetaEditTime(self, label: str):
+        return self.getMeta(key=self.META_EDIT_TIME, label=label)
+
+    def getMetaComment(self, label: str):
+        return self.getMeta(key=self.META_COMMENT, label=label)
+
+    def getInfoInLabels(self, label: str):
+        return ["labels"] + [label] + ["info"]
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageData.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageData.py
new file mode 100644
index 000000000..aec113647
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageData.py
@@ -0,0 +1,159 @@
+from datetime import datetime
+
+from ReviewerLibs.MONAILabelReviewerEnum import SegStatus
+from ReviewerLibs.SegmentationMeta import SegmentationMeta
+
+"""
+ImageData is a container for each segmentation/image.
+Such ImageData contains the meta data of corresponding segmentation/image (e.g. fileName, checkSum, comment, etc.)
+Each change (regarding the review process) will be monitored within ImageData.
+Once a user select the next segmentation during review the information in ImageData will be send to MONAI-Server in order
+to persist the data in datastore_v2.json file.
+
+"""
+
+
+class ImageData:
+    def __init__(self, name, fileName, nodeName, checkSum, segmented, timeStamp, comment=""):
+        self.name: str = name
+        self.fileName: str = fileName
+        self.nodeName: str = nodeName
+        self.checkSum: str = checkSum
+        self.segmented: bool = segmented
+        self.timeStamp: int = timeStamp
+        self.comment: str = comment
+
+        self.STATUS = SegStatus()
+
+        self.client_id: str = None
+        self.segmentationFileName: str = None
+        self.tempDirectory: str = None
+        self.segmentationMeta: SegmentationMeta = None
+
+    def getName(self) -> str:
+        return self.name
+
+    def getFileName(self) -> str:
+        return self.fileName
+
+    def getNodeName(self) -> str:
+        return self.nodeName
+
+    def getCheckSum(self) -> str:
+        return self.checkSum
+
+    def getClientId(self) -> str:
+        return self.client_id
+
+    def getTimeStamp(self) -> int:
+        return self.timeStamp
+
+    def getTime(self) -> str:
+        return str(datetime.fromtimestamp(self.timeStamp))
+
+    def isSegemented(self) -> bool:
+        return self.segmented
+
+    def getComment(self) -> str:
+        return self.comment
+
+    def getStatus(self) -> str:
+        if self.isSegemented() is False:
+            return self.STATUS.NOT_SEGMENTED
+        if self.hasSegmentationMeta() is False:
+            return ""
+        if self.hasSegmentationMeta():
+            return self.segmentationMeta.getStatus()
+
+    def getApprovedBy(self) -> str:
+        if self.isSegemented() is False:
+            return ""
+        if self.hasSegmentationMeta() is False:
+            return ""
+        return self.segmentationMeta.getApprovedBy()
+
+    def isApproved(self) -> bool:
+        if self.hasSegmentationMeta() is False:
+            return False
+        if self.getStatus() == self.STATUS.APPROVED:
+            return True
+        return False
+
+    def isFlagged(self) -> bool:
+        if self.hasSegmentationMeta() is False:
+            return False
+        if self.getStatus() == self.STATUS.FLAGGED:
+            return True
+        return False
+
+    def getLevel(self) -> str:
+        if self.isSegemented() is False:
+            return ""
+        if self.hasSegmentationMeta() is False:
+            return ""
+        return self.segmentationMeta.getLevel()
+
+    def setSegmentationFileName(self, fileName: str):
+        self.segmentationFileName = fileName
+
+    def getSegmentationFileName(self) -> str:
+        return self.segmentationFileName
+
+    def setClientId(self, client_id: str):
+        self.client_id = client_id
+
+    def setSegmentationMeta(self, status="", level="", approvedBy="", comment=""):
+        self.segmentationMeta = SegmentationMeta()
+        self.segmentationMeta.build(status=status, level=level, approvedBy=approvedBy, comment=comment)
+
+    def isEqualSegmentationMeta(self, status="", level="", approvedBy="", comment="") -> bool:
+        if (
+            self.segmentationMeta is None
+            and self.isBlank(status)
+            and self.isBlank(level)
+            and self.isBlank(approvedBy)
+            and self.isBlank(comment)
+        ):
+            return True
+
+        if self.segmentationMeta is None:
+            self.setSegmentationMeta(status, level, approvedBy, comment)
+            return False
+
+        return self.segmentationMeta.isEqual(status=status, level=level, approvedBy=approvedBy, comment=comment)
+
+    def updateSegmentationMeta(self, status="", level="", approvedBy="", comment=""):
+        if self.segmentationMeta is None:
+            self.segmentationMeta = SegmentationMeta()
+            self.segmentationMeta.build(status=status, level=level, approvedBy=approvedBy, comment=comment)
+            return
+
+        self.segmentationMeta.setStatus(status)
+        self.segmentationMeta.setLevel(level)
+        self.segmentationMeta.setApprovedBy(approvedBy)
+        self.segmentationMeta.setComment(comment)
+
+    def isBlank(self, string) -> bool:
+        return not (string and string.strip())
+
+    def getMeta(self) -> str:
+        if self.segmentationMeta is None:
+            return None
+        return self.segmentationMeta.getMeta()
+
+    def hasSegmentationMeta(self) -> bool:
+        return self.segmentationMeta is not None
+
+    def display(self):
+        print("name: ", self.name)
+        print("fileName: ", self.fileName)
+        print("nodeName: ", self.nodeName)
+        print("checksum: ", self.checkSum)
+        print("isSegmented: ", self.segmented)
+        print("getTimeStamp: ", self.getTime())
+        if self.isSegemented():
+            print("Client Id: ", self.client_id)
+            print("segmentationFileName: ", self.segmentationFileName)
+            print("=== Segmentation Meta ====")
+        if self.hasSegmentationMeta():
+            self.segmentationMeta.display()
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataController.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataController.py
new file mode 100644
index 000000000..5b98c040b
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataController.py
@@ -0,0 +1,210 @@
+import datetime
+import logging
+from typing import Dict, List
+
+import requests
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.ImageDataExtractor import ImageDataExtractor
+from ReviewerLibs.JsonParser import JsonParser
+from ReviewerLibs.MonaiServerREST import MonaiServerREST
+
+"""
+ImageDataController manages all data processing and data transactions via
+
+    1. MonaiServerREST (requests and peristency of meta data, image data, segmentation data from monai server)
+    2. ImageDataExtractor (handling of logical operations coming from set of imageData)
+    3. JsonParser (parsing information from datastore_v2.json)
+    4. ImageData (container which caches information of single image and corresponding segmenation information and meta data)
+
+content of meta data:
+    1. "status" (flagged or approved)
+    2. "approvedBy" (name of reviewer)
+    3. "level" (level of difficulty of segmentation: easy, medium, hard)
+    4. "comment" (any comment on image and segmenation)
+    5. "editTime"
+
+    list of meta information can be extanded
+
+"""
+
+
+class ImageDataController:
+    def __init__(self):
+        self.monaiServerREST: MonaiServerREST = None
+        self.imageDataExtractor: ImageDataExtractor = None
+        self.temp_dir = None
+
+    def getServerUrl(self) -> str:
+        return self.monaiServerREST.getServerUrl()
+
+    def getCurrentTime(self) -> datetime:
+        return datetime.datetime.now()
+
+    def setMonaiServer(self, serverUrl: str):
+        self.monaiServerREST = MonaiServerREST(serverUrl)
+
+    def connectToMonaiServer(self, serverUrl: str) -> bool:
+        self.setMonaiServer(serverUrl)
+        return self.monaiServerREST.checkServerConnection()
+
+    def getMapIdToImageData(self) -> Dict[str, ImageData]:
+        """
+        Returns dictionary (Dict[str:ImageData]) which maps id to Imagedata-object
+        """
+        jsonObj = self.monaiServerREST.requestDataStoreInfo()
+        if jsonObj is None:
+            return None
+
+        # Parse json file to ImageData object
+        jsonParser = JsonParser(jsonObj)
+        jsonParser.init()
+        mapIdToImageData = jsonParser.getMapIdToImageData()
+        return mapIdToImageData
+
+    def initMetaDataProcessing(self) -> bool:
+        """
+        Passes mapIdToImageData to ImageDataExtractor object in order to process the meta information
+        for each imageData
+
+        returns True if it was successful
+        else False
+        """
+        mapIdToImageData = self.getMapIdToImageData()
+        if mapIdToImageData is None:
+            return False
+        self.imageDataExtractor = ImageDataExtractor(mapIdToImageData)
+        self.imageDataExtractor.init()
+        return True
+
+    def getStatistics(self) -> dict:
+        """
+        returns a map which contains statistical values which are comming from ImageDataExtractor object
+        """
+        statistics = {}
+        # ProgressBar: TOTAL
+        statistics["segmentationProgress"] = self.imageDataExtractor.getSegmentationProgessInPercentage()
+        statistics["idxTotalSegmented"] = self.imageDataExtractor.getSegmentationVsTotalStr()
+        statistics["idxTotalApproved"] = self.imageDataExtractor.getApprovalVsTotal()
+        statistics["progressPercentage"] = self.imageDataExtractor.getApprovalProgressInPercentage()
+
+        # ProgressBar: FILTER (incl. idxTotalSegmented, idxTotalApproved)
+        statistics["segmentationProgressAllPercentage"] = self.imageDataExtractor.getSegmentationProgessInPercentage()
+        statistics["approvalProgressPercentage"] = self.imageDataExtractor.getApprovalProgressInPercentage()
+
+        return statistics
+
+    # returns only client id of those images which are segemented
+    def getClientIds(self) -> List[str]:
+        return self.imageDataExtractor.getClientIds()
+
+    def getReviewers(self) -> List[str]:
+        return self.imageDataExtractor.getReviewers()
+
+    # Section: Loading images
+    def getAllImageData(self, segmented, isNotSegmented, isApproved, isFlagged) -> List[ImageData]:
+        return self.imageDataExtractor.getAllImageData(
+            segmented=segmented, notSegmented=isNotSegmented, approved=isApproved, flagged=isFlagged
+        )
+
+    def getImageDataByClientId(self, selectedClientId, isApproved, isFlagged) -> List[ImageData]:
+        return self.imageDataExtractor.getImageDataByClientId(
+            clientId=selectedClientId, approved=isApproved, flagged=isFlagged
+        )
+
+    def getPercentageApproved(self, selectedClientId):
+        percentageApprovedOfClient, idxApprovedOfClient = self.imageDataExtractor.getPercentageApproved(
+            selectedClientId
+        )
+        return percentageApprovedOfClient, idxApprovedOfClient
+
+    def getPercentageSemgmentedByClient(self, selectedClientId):
+        percentageSemgmentedByClient, idxSegmentedByClient = self.imageDataExtractor.getPercentageSemgmentedByClient(
+            selectedClientId
+        )
+        return percentageSemgmentedByClient, idxSegmentedByClient
+
+    # Section: Search Image
+    def getMultImageDataByIds(self, imageIds) -> Dict[str, ImageData]:
+        return self.imageDataExtractor.getMultImageDataByIds(imageIds)
+
+    def searchByAnnotatorReviewer(
+        self, selectedAnnotator: str, selectedReviewer: str, isApproved: bool, isFlagged: bool
+    ) -> Dict[str, ImageData]:
+        """
+        returns set of imageData (imageId mapped to ImageData) according to given filter options
+        """
+        idToImageData: Dict[str, ImageData] = {}
+        imageIdsOfAnnotator = None
+        if selectedAnnotator == "All" and selectedReviewer != "All":
+            imageIdsOfAnnotator = self.imageDataExtractor.getImageDataByReviewer(
+                selectedReviewer, isApproved, isFlagged
+            )
+
+        if selectedAnnotator != "All" and selectedReviewer == "All":
+            imageIdsOfAnnotator = self.imageDataExtractor.getImageDataByClientId(
+                selectedAnnotator, isApproved, isFlagged
+            )
+
+        if selectedReviewer == "All" and selectedAnnotator == "All":
+            imageIdsOfAnnotator = self.imageDataExtractor.getAllImageData(
+                segmented=True, notSegmented=False, approved=isApproved, flagged=isFlagged
+            )
+
+        if selectedReviewer != "All" and selectedAnnotator != "All":
+            imageIdsOfAnnotator = self.imageDataExtractor.getImageDataByClientAndReviewer(
+                selectedAnnotator, selectedReviewer, isApproved, isFlagged
+            )
+
+        if imageIdsOfAnnotator is None:
+            return idToImageData
+
+        for imageData in imageIdsOfAnnotator:
+            idToImageData[imageData.getName()] = imageData
+
+        return idToImageData
+
+    def getImageDataByLevel(self, isEasy: bool, isMedium: bool, isHard: bool) -> Dict[str, ImageData]:
+        """
+        returns set of imageData (imageId mapped to ImageData) according to given level of difficulty
+        """
+        imageIdsOfAnnotator = self.imageDataExtractor.getImageDataByLevel(
+            isEasy=isEasy, isMedium=isMedium, isHard=isHard
+        )
+        return imageIdsOfAnnotator
+
+    # Section: Dicom stream
+    def updateLabelInfo(self, imageId, updatedMetaJson) -> bool:
+        """
+        sends meta information via http request to monai server
+        in order to perist the information in datastore_v2.json file
+
+        returns True if successfully sent http request
+        else False
+        """
+        repsonseCode = self.monaiServerREST.updateLabelInfo(image_id=imageId, params=updatedMetaJson)
+        if repsonseCode == 200:
+            logging.info(f"{self.getCurrentTime()}: Successfully persist meta data for image (id='{imageId}')")
+            return True
+        else:
+            logging.info(
+                "{}: Failed meta date persistence for image (id='{}', response code = '{}')".format(
+                    self.getCurrentTime(), imageId, repsonseCode
+                )
+            )
+            return False
+
+    def reuqestSegmentation(self, image_id) -> requests.models.Response:
+        """
+        after sending request to monai server
+        rerturns response body (img_blob) which contains the segmentation data
+        """
+        img_blob = self.monaiServerREST.requestSegmentation(image_id)
+        logging.info(
+            "{}: Segmentation successfully requested from MONAIServer (image id: {})".format(
+                self.getCurrentTime(), image_id
+            )
+        )
+        return img_blob
+
+    def getDicomDownloadUri(self, image_id: str) -> str:
+        return self.monaiServerREST.getDicomDownloadUri(image_id)
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataExtractor.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataExtractor.py
new file mode 100644
index 000000000..01eab9c1f
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/ImageDataExtractor.py
@@ -0,0 +1,343 @@
+import datetime
+import logging
+from typing import Dict, List
+
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.MONAILabelReviewerEnum import Level
+
+"""
+ImageDataExtractor gets dictionary (mapping from id to ImageData from JsonParser) and caches
+    Mapping:
+        - imageIds TO ImageData,
+        - client TO list of imageIds
+    List:
+        - imageIds of all images which are not segemented yet
+        - imageIds of all images which are approved
+        - all reviewers
+
+Each modification during review process will be stored in corresponding ImageData
+ImageDataExtractor provides the meta data across all ImageData-Containers when the user selects the filter option
+"""
+
+
+class ImageDataExtractor:
+    def __init__(self, nameToImageData: dict):
+        self.LEVEL = Level()
+        self.nameToImageData: Dict[str, ImageData] = nameToImageData
+
+        self.clientToImageIds: Dict[str, list] = {}
+        self.idsOfNotSegmented: List[str] = []
+        self.idsOfApprovedSementations: List[str] = []
+        self.reviewers: List[str] = []
+
+    def init(self):
+        self.groupImageDataByClientId()
+        self.extractAllReviewers()
+        self.extractNotSegmentedImageIds()
+
+    def getCurrentTime(self) -> datetime:
+        return datetime.datetime.now()
+
+    def groupImageDataByClientId(self):
+        for imageId, imageData in self.nameToImageData.items():
+            if imageData.isSegemented():
+
+                clientId = imageData.getClientId()
+                if clientId:
+                    if clientId not in self.clientToImageIds:
+                        self.clientToImageIds[clientId] = []
+                    self.clientToImageIds[clientId].append(imageId)
+
+    def extractAllReviewers(self):
+        for imageId, imageData in self.nameToImageData.items():
+            if imageData.isSegemented():
+                reviewer = imageData.getApprovedBy()
+                if reviewer not in self.reviewers and reviewer != "":
+                    self.reviewers.append(reviewer)
+
+    def extractNotSegmentedImageIds(self):
+        for imageId, imageData in self.nameToImageData.items():
+            if imageData.isSegemented() is False:
+                self.idsOfNotSegmented.append(imageId)
+
+    def getTotalNumImages(self) -> int:
+        return len(self.nameToImageData)
+
+    def getImageDataIds(self) -> List[str]:
+        return [*self.nameToImageData.keys()]
+
+    def getClientIds(self) -> List[str]:
+        return [*self.clientToImageIds.keys()]
+
+    def getReviewers(self) -> List[str]:
+        return self.reviewers
+
+    def getImageDataNotsegmented(self) -> List[ImageData]:
+        """
+        returns list of ImageData of corresponingd image studies wich has not been segemeted
+        """
+        notSegmented = []
+        for id in self.idsOfNotSegmented:
+            imageData = self.nameToImageData[id]
+            notSegmented.append(imageData)
+        return notSegmented
+
+    def getNumOfNotSegmented(self) -> int:
+        return len(self.idsOfNotSegmented)
+
+    def getNumOfSegmented(self) -> int:
+        count = 0
+        for client, idList in self.clientToImageIds.items():
+            count += len(idList)
+        return count
+
+    def getSegmentationProgessInPercentage(self) -> int:
+        """
+        returns percentage of already segmented images out of all available images
+        """
+        segmentedCount = self.getNumOfSegmented()
+        float_Num = segmentedCount / self.getTotalNumImages()
+        return int(float_Num * 100)
+
+    def getSegmentationVsTotalStr(self) -> str:
+        """
+        returns the index of subjected imageData within imageData data set
+        """
+        segmentedCount = self.getNumOfSegmented()
+        idxTotalSegmented: str = f"{segmentedCount}/{self.getTotalNumImages()}"
+        return idxTotalSegmented
+
+    def getApprovalProgressInPercentage(self) -> int:
+        """
+        returns percentage of already approved imageData out of all available imageData
+        """
+        approvalCount = self.getNumApprovedSegmentation()
+        fraction = approvalCount / self.getTotalNumImages()
+        return int(fraction * 100)
+
+    def getApprovalVsTotal(self) -> str:
+        approvalCount = self.getNumApprovedSegmentation()
+        idxTotalApproved: str = f"{approvalCount}/{self.getTotalNumImages()}"
+        return idxTotalApproved
+
+    def getAllImageData(self, segmented=False, notSegmented=False, approved=False, flagged=False) -> List[ImageData]:
+        """
+        returns fitered list of imageData which are filtered according to input parameters
+        """
+        if (
+            (notSegmented and segmented)
+            or (approved and flagged)
+            or (notSegmented and approved)
+            or (notSegmented and flagged)
+        ):
+            logging.warning(
+                "{}: Selected filter options are not valid: segmented='{}' | notSegmented='{}' | approved='{}' | flagged='{}')".format(
+                    self.getCurrentTime(), segmented, notSegmented, approved, flagged
+                )
+            )
+            return None
+
+        if notSegmented is False and segmented is False and approved is False and flagged is False:
+            return [*self.nameToImageData.values()]
+
+        selectedImageData = []
+        for imagedata in self.nameToImageData.values():
+
+            if notSegmented is True and segmented is False and imagedata.isSegemented() is False:
+                selectedImageData.append(imagedata)
+                continue
+
+            if (
+                segmented is imagedata.isSegemented()
+                and approved is imagedata.isApproved()
+                and flagged is imagedata.isFlagged()
+            ):
+                selectedImageData.append(imagedata)
+                continue
+
+        return selectedImageData
+
+    def getImageDataByClientId(self, clientId: str, approved=False, flagged=False) -> List[ImageData]:
+        """
+        returns fitered list of imageData which are filtered according to client (=annotator) and parameters (approved, flagged)
+        """
+
+        if clientId == "":
+            return None
+        if approved and flagged:
+            logging.warning(
+                "{}: Selected filter options are not valid: approved='{}' and flagged='{}')".format(
+                    self.getCurrentTime(), approved, flagged
+                )
+            )
+            return None
+
+        imageIds = self.clientToImageIds[clientId]
+        imageDataList = []
+        for id in imageIds:
+            if id not in self.nameToImageData:
+                logging.error(
+                    "{}: Image data [id = {}] not found for [clientId = {}] ".format(
+                        self.getCurrentTime(), id, clientId
+                    )
+                )
+                continue
+            imageData = self.nameToImageData[id]
+            if approved and imageData.isApproved() is False:
+                continue
+            if flagged and imageData.isFlagged() is False:
+                continue
+
+            imageDataList.append(imageData)
+        return imageDataList
+
+    def getImageDataByClientAndReviewer(
+        self, clientId: str, reviewerId: str, approved=False, flagged=False
+    ) -> List[ImageData]:
+        """
+        returns fitered list of imageData which are filtered according to client (=annotator) and reviewer and parameters (approved, flagged)
+        """
+
+        imageDatas = self.getImageDataByClientId(clientId, approved, flagged)
+        filteredByRewiewer = list(filter(lambda imageData: (imageData.getApprovedBy() == reviewerId), imageDatas))
+        return filteredByRewiewer
+
+    def getImageDataByReviewer(self, reviewerId: str, approved=False, flagged=False) -> List[ImageData]:
+        if reviewerId == "":
+            return None
+        if approved and flagged:
+            logging.warning(
+                "{}: Selected filter options are not valid: approved='{}' and flagged='{}')".format(
+                    self.getCurrentTime(), approved, flagged
+                )
+            )
+            return None
+
+        filteredImageDataList = []
+
+        for imageData in self.nameToImageData.values():
+            if imageData.isSegemented() is False:
+                continue
+            if approved and imageData.isApproved() is False:
+                continue
+            if flagged and imageData.isFlagged() is False:
+                continue
+            if imageData.getApprovedBy() == reviewerId:
+                filteredImageDataList.append(imageData)
+
+        return filteredImageDataList
+
+    def getImageDataByLevel(self, isEasy: bool, isMedium: bool, isHard: bool) -> Dict[str, ImageData]:
+        """
+        returns fitered list of imageData which are filtered according to level of difficulty (regarding segmentation): easy, medium, hard
+        """
+        filteredImageData = {}
+        for id, imagedata in self.nameToImageData.items():
+            if imagedata is None:
+                continue
+            if imagedata.isSegemented == "False":
+                continue
+            if isEasy and imagedata.getLevel() == self.LEVEL.EASY:
+                filteredImageData[id] = imagedata
+                continue
+
+            if isMedium and imagedata.getLevel() == self.LEVEL.MEDIUM:
+                filteredImageData[id] = imagedata
+                continue
+
+            if isHard and imagedata.getLevel() == self.LEVEL.HARD:
+                filteredImageData[id] = imagedata
+        return filteredImageData
+
+    def getSingleImageDataById(self, imageId: str) -> ImageData:
+        """
+        returns imageData by given imageId
+        """
+        if self.isBlank(imageId):
+            return None
+        if imageId not in self.nameToImageData:
+            logging.warning(f"{self.getCurrentTime()}: Image data for requested id [{imageId}] not found")
+            return None
+        return self.nameToImageData[imageId]
+
+    def getMultImageDataByIds(self, ids: List[str]) -> Dict[str, ImageData]:
+        """
+        returns multiple imageData by given list of imageId
+        """
+        idToimageData: Dict[str, ImageData] = {}
+        if len(ids) == 0:
+            logging.warning(f"{self.getCurrentTime()}: Given id list is empty.")
+            return idToimageData
+        for id in ids:
+            imageData = self.getSingleImageDataById(id)
+            if imageData is None:
+                continue
+            idToimageData[imageData.getName()] = imageData
+        return idToimageData
+
+    def getNumApprovedSegmentation(self) -> int:
+        """
+        returns total number of imageData which are approved
+        """
+        count = self.countApprovedSegmentation(self.nameToImageData.values())
+        return count
+
+    def countApprovedSegmentation(self, imageDatas: List[ImageData]) -> int:
+        if imageDatas is None:
+            return 0
+        approvedCount = 0
+        for imageData in imageDatas:
+            if imageData is None:
+                continue
+            if imageData.isApproved():
+                approvedCount += 1
+        return approvedCount
+
+    def getPercentageApproved(self, clientId: str):
+        """
+        returns the percentage of images that have already been approved by given client (=Annotator)
+        and the value: (total number of images approved by given client (=Annotator))/(total number of imageData)
+        """
+        listImageData = self.getImageDataByClientId(clientId=clientId)
+        approvedCount = self.countApprovedSegmentation(listImageData)
+        if len(listImageData) == 0:
+            logging.warning(f"{self.getCurrentTime()}: There are no images")
+            return 0
+        fraction = approvedCount / len(listImageData)
+        precentage = int(fraction * 100)
+        idxApprovedOfClient: str = f"{approvedCount}/{len(listImageData)}"
+        return precentage, idxApprovedOfClient
+
+    def getPercentageSemgmentedByClient(self, clientId: str):
+        """
+        returns the percentage of images that have already been segmented by given client (=Annotator)
+        and the value: (total number of images segmented by given client (=Annotator))/(total number of imageData)
+        """
+        numSegementedByClient = len(self.clientToImageIds[clientId])
+        fraction = numSegementedByClient / self.getTotalNumImages()
+        precentage = int(fraction * 100)
+        idxSegmentedByClient: str = f"{numSegementedByClient}/{self.getTotalNumImages()}"
+        return precentage, idxSegmentedByClient
+
+    def getApprovedSegmentationIds(self) -> List[str]:
+        """
+        returns list of ids of all approved imageData
+        """
+        idsOfApprovedSementations = []
+        for imageId, imageData in self.nameToImageData.items():
+            if imageData.isApproved():
+                idsOfApprovedSementations.append(imageId)
+        return idsOfApprovedSementations
+
+    def getSegmentedImageIds(self) -> List[str]:
+        """
+        returns list of ids of all segmented imageData
+        """
+        idsOfSegmented = []
+        for imageId, imageData in self.nameToImageData.items():
+            if imageData.isSegemented():
+                idsOfSegmented.append(imageId)
+        return idsOfSegmented
+
+    def isBlank(self, string) -> bool:
+        return not (string and string.strip())
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/JsonParser.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/JsonParser.py
new file mode 100644
index 000000000..4edd236d9
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/JsonParser.py
@@ -0,0 +1,192 @@
+from typing import Dict
+
+from ReviewerLibs.DataStoreKeys import DataStoreKeys
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.MONAILabelReviewerEnum import Label
+
+"""
+JsonParser parses the datastore.json file
+and caches the information in dictionary: Mapping from id to ImageData
+"""
+
+
+class JsonParser:
+    def __init__(self, jsonObject: dict):
+        self.LABEL = Label()
+        self.SEGMENTATION_META = "segmentationMeta"
+
+        self.dataStoreKeys = DataStoreKeys()
+        self.jsonObject = jsonObject
+        self.mapIdToImageData: Dict[str, ImageData] = {}
+
+    def init(self):
+        self.parseJsonToImageData()
+
+    def getValueByKey(self, keyArr: list, jsonObj: dict):
+        if len(keyArr) == 0:
+            return ""
+        for key in keyArr:
+            if key not in jsonObj:
+                return ""
+            jsonObj = jsonObj[key]
+        return jsonObj
+
+    def getFileName(self, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.FILENAME, obj)
+
+    def getNodeName(self, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.NODE_NAME, obj)
+
+    def getCheckSum(self, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.CHECKSUM, obj)
+
+    def getTimeStamp(self, obj: dict) -> int:
+        if self.hasKeyAnnotate(obj):
+            return self.getValueByKey(self.dataStoreKeys.TIMESTAMP_ANNOTATE, obj)
+        if self.hasKeyRandom(obj):
+            return self.getValueByKey(self.dataStoreKeys.TIMESTAMP_RANDOM, obj)
+        return self.getValueByKey(self.dataStoreKeys.TIMESTAMP, obj)
+
+    def getInfo(self, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.IMAGE_INFO, obj)
+
+    def getInfoInLabels(self, label: str, obj: dict) -> Dict[str, str]:
+        keys = self.dataStoreKeys.getInfoInLabels(label)
+        return self.getValueByKey(keys, obj)
+
+    def hasLabels(self, obj: dict) -> bool:
+        lablesSection = self.getValueByKey(self.dataStoreKeys.LABELS, obj)
+        if len(lablesSection) == 0:
+            return False
+        return True
+
+    def hasSegemantatorsId(self, obj: dict) -> bool:
+        labels = self.getValueByKey(self.dataStoreKeys.LABELS, obj)
+
+        if len(labels) == 0:
+            return False
+
+        if self.dataStoreKeys.FINAL not in labels:
+            return False
+
+        final = self.getValueByKey(self.dataStoreKeys.LABELS_FINAL, obj)
+        if self.dataStoreKeys.INFO not in final:
+            return False
+
+        info = self.getValueByKey(self.dataStoreKeys.LABELS_FINAL_INFO, obj)
+        if self.dataStoreKeys.LABEL_INFO not in info:
+            return False
+
+        labelsInfo = self.getValueByKey(self.dataStoreKeys.LABELS_FINAL_INFO_LABELS_INFO, obj)
+        if len(labelsInfo) == 0:
+            return False
+        return True
+
+    def isSegmented(self, obj: dict) -> bool:
+        labels = self.getValueByKey(self.dataStoreKeys.LABELS, obj)
+        if len(labels) == 0:
+            return False
+        if self.dataStoreKeys.FINAL not in labels:
+            return False
+        return True
+
+    def hasKeyFinal(self, obj: dict):
+        labelsDict = self.getValueByKey(self.dataStoreKeys.LABELS, obj)
+        return self.dataStoreKeys.FINAL in labelsDict
+
+    def hasKeyOriginal(self, obj: dict):
+        labelsDict = self.getValueByKey(self.dataStoreKeys.LABELS, obj)
+        return self.dataStoreKeys.ORIGINAL in labelsDict
+
+    def getSegmentationName(self, obj: dict) -> dict:
+        if self.hasKeyFinal(obj):
+            return self.getValueByKey(self.dataStoreKeys.SEGMENTATION_NAME_BY_FINAL, obj)
+        if self.hasKeyOriginal(obj):
+            return self.getValueByKey(self.dataStoreKeys.SEGMENTATION_NAME_BY_ORIGINAL, obj)
+
+    def hasKeyAnnotate(self, obj: dict):
+        strategyDict = self.getValueByKey(self.dataStoreKeys.STRATEGY, obj)
+        return self.dataStoreKeys.ANNOTATE in strategyDict
+
+    def hasKeyRandom(self, obj: dict):
+        strategyDict = self.getValueByKey(self.dataStoreKeys.STRATEGY, obj)
+        return self.dataStoreKeys.RANDOM in strategyDict
+
+    def getClientId(self, obj: dict) -> str:
+        if self.hasKeyAnnotate(obj):
+            return self.getValueByKey(self.dataStoreKeys.CLIENT_ID_BY_ANNOTATE, obj)
+        if self.hasKeyRandom(obj):
+            return self.getValueByKey(self.dataStoreKeys.CLIENT_ID_BY_RANDOM, obj)
+        if self.hasSegemantatorsId(obj):
+            return self.getValueByKey(self.dataStoreKeys.CLIENT_ID, obj)
+        return "Segmented without annotator's id"
+
+    def getMetaStatus(self, label: str, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.getMetaStatus(label), obj)
+
+    def getMetaLevel(self, label: str, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.getMetaLevel(label), obj)
+
+    def getMetaApprovedBy(self, label: str, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.getMetaApprovedBy(label), obj)
+
+    def getMetaEditTime(self, label: str, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.getMetaEditTime(label), obj)
+
+    def getMetaComment(self, label: str, obj: dict) -> str:
+        return self.getValueByKey(self.dataStoreKeys.getMetaComment(label), obj)
+
+    def parseJsonToImageData(self):
+        objects = self.jsonObject[self.dataStoreKeys.OBJECT]
+        counter = 0
+        for key, value in objects.items():
+            imageData = self.jsonToImageData(key, value)
+            self.mapIdToImageData[key] = imageData
+            counter += 1
+
+    def jsonToImageData(self, key, value):
+        fileName = self.getFileName(value)
+        nodeName = self.getNodeName(value)
+        checksum = self.getCheckSum(value)
+        isSegmented = self.isSegmented(value)
+        timeStamp = self.getTimeStamp(value)
+        imageData = ImageData(
+            name=key,
+            fileName=fileName,
+            nodeName=nodeName,
+            checkSum=checksum,
+            segmented=isSegmented,
+            timeStamp=timeStamp,
+        )
+
+        if isSegmented:
+            segName = self.getSegmentationName(value)
+            imageData.setSegmentationFileName(segName)
+
+            clientId = self.getClientId(value)
+            imageData.setClientId(clientId)
+
+        if self.hasLabels(value) is False:
+            return imageData
+
+        label = ""
+        if self.hasKeyFinal(value):
+            label = self.LABEL.FINAL
+
+        if self.hasKeyOriginal(value):
+            label = self.LABEL.ORGINAL
+
+        info = self.getInfoInLabels(label=label, obj=value)
+        if self.hasSegmentationMeta(info):
+            status = self.getMetaStatus(label, value)
+            level = self.getMetaLevel(label, value)
+            approvedBy = self.getMetaApprovedBy(label, value)
+            comment = self.getMetaComment(label, value)
+            imageData.setSegmentationMeta(status, level, approvedBy, comment)
+        return imageData
+
+    def hasSegmentationMeta(self, info: dict) -> bool:
+        return self.SEGMENTATION_META in info.keys()
+
+    def getMapIdToImageData(self):
+        return self.mapIdToImageData
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerEnum.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerEnum.py
new file mode 100644
index 000000000..fbf287e0a
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerEnum.py
@@ -0,0 +1,19 @@
+class SegStatus:
+    def __init__(self):
+        self.NOT_SEGMENTED = "not segmented"
+        self.APPROVED = "approved"
+        self.SEGMENTED = "segmented"
+        self.FLAGGED = "flagged"
+
+
+class Level:
+    def __init__(self):
+        self.EASY = "easy"
+        self.MEDIUM = "medium"
+        self.HARD = "hard"
+
+
+class Label:
+    def __init__(self):
+        self.ORGINAL = "original"
+        self.FINAL = "final"
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerLogic.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerLogic.py
new file mode 100644
index 000000000..fc3347c21
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MONAILabelReviewerLogic.py
@@ -0,0 +1,168 @@
+import datetime
+import logging
+import os
+import tempfile
+from typing import Dict, List
+
+import requests
+import SampleData
+import slicer
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.ImageDataController import ImageDataController
+from slicer.ScriptedLoadableModule import *
+
+
+class MONAILabelReviewerLogic(ScriptedLoadableModuleLogic):
+    """This class should implement all the actual
+    computation done by module.  The interface
+    should be such that other python code can import
+    this class and make use of the functionality without
+    requiring an instance of the Widget.
+    Uses ScriptedLoadableModuleLogic base class, available at:
+    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+    """
+
+    def __init__(self):
+        """
+        Called when the logic class is instantiated. Can be used for initializing member variables.
+        """
+        ScriptedLoadableModuleLogic.__init__(self)
+        self.temp_dir = None
+
+        self.imageDataController: ImageDataController = ImageDataController()
+
+    # Section: Server
+    def getServerUrl(self) -> str:
+        return self.imageDataController.getServerUrl()
+
+    def getCurrentTime(self) -> datetime:
+        return datetime.datetime.now()
+
+    def connectToMonaiServer(self, serverUrl: str) -> bool:
+        return self.imageDataController.connectToMonaiServer(serverUrl)
+
+    def getMapIdToImageData(self) -> Dict[str, ImageData]:
+        """
+        Returns dictionary (Dict[str:ImageData]) which maps id to Imagedata-object
+        """
+        return self.imageDataController.getMapIdToImageData()
+
+    def initMetaDataProcessing(self) -> bool:
+        return self.imageDataController.initMetaDataProcessing()
+
+    def getStatistics(self) -> dict:
+        return self.imageDataController.getStatistics()
+
+    def getClientIds(self) -> List[str]:
+        return self.imageDataController.getClientIds()
+
+    def getReviewers(self) -> List[str]:
+        return self.imageDataController.getReviewers()
+
+    # Section: Loading images
+    def getAllImageData(self, segmented, isNotSegmented, isApproved, isFlagged) -> List[ImageData]:
+        return self.imageDataController.getAllImageData(segmented, isNotSegmented, isApproved, isFlagged)
+
+    def getImageDataByClientId(self, selectedClientId, isApproved, isFlagged) -> List[ImageData]:
+        return self.imageDataController.getImageDataByClientId(selectedClientId, isApproved, isFlagged)
+
+    def getPercentageApproved(self, selectedClientId):
+        percentageApprovedOfClient, idxApprovedOfClient = self.imageDataController.getPercentageApproved(
+            selectedClientId
+        )
+        return percentageApprovedOfClient, idxApprovedOfClient
+
+    def getPercentageSemgmentedByClient(self, selectedClientId):
+        percentageSemgmentedByClient, idxSegmentedByClient = self.imageDataController.getPercentageSemgmentedByClient(
+            selectedClientId
+        )
+        return percentageSemgmentedByClient, idxSegmentedByClient
+
+    # Section: Search Image
+    def getMultImageDataByIds(self, idList) -> Dict[str, ImageData]:
+        return self.imageDataController.getMultImageDataByIds(idList)
+
+    def searchByAnnotatorReviewer(
+        self, selectedAnnotator: str, selectedReviewer: str, isApproved: bool, isFlagged: bool
+    ) -> Dict[str, ImageData]:
+        return self.imageDataController.searchByAnnotatorReviewer(
+            selectedAnnotator, selectedReviewer, isApproved, isFlagged
+        )
+
+    def searchByLevel(self, isEasy: bool, isMedium: bool, isHard: bool) -> Dict[str, ImageData]:
+        return self.imageDataController.getImageDataByLevel(isEasy=isEasy, isMedium=isMedium, isHard=isHard)
+
+    # Section: Dicom stream
+    def updateLabelInfo(self, imageId, updatedMetaJson):
+        self.imageDataController.updateLabelInfo(imageId, updatedMetaJson)
+
+    def loadDicomAndSegmentation(self, imageData):
+        """
+        Loads original Dicom image and Segmentation into slicer window
+        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).
+        """
+        # Request dicom
+        image_name = imageData.getFileName()
+        image_id = imageData.getName()
+        node_name = imageData.getNodeName()
+        checksum = imageData.getCheckSum()
+        logging.info(
+            "{}: Request Data  image_name='{}', node_name='{}', image_id='{}', checksum='{}'".format(
+                self.getCurrentTime(), image_name, node_name, image_id, checksum
+            )
+        )
+
+        self.requestDicomImage(image_id, image_name, node_name, checksum)
+        self.setTempFolderDir()
+
+        # Request segmentation
+        if imageData.isSegemented():
+            segmentationFileName = imageData.getSegmentationFileName()
+            img_blob = self.imageDataController.reuqestSegmentation(image_id)
+            destination = self.storeSegmentation(img_blob, segmentationFileName, self.temp_dir.name)
+            self.displaySegmention(destination)
+            os.remove(destination)
+            logging.info(f"{self.getCurrentTime()}: Removed file at {destination}")
+
+    def storeSegmentation(
+        self, response: requests.models.Response, segmentationFileName: str, tempDirectory: str
+    ) -> str:
+        """
+        stores loaded segmenation temporarily in local directory
+        Parameters:
+            response (requests.models.Response): contains segmentation data
+            image_id (str): image id of segmentation
+        """
+        segmentation = response.content
+        destination = self.getPathToStore(segmentationFileName, tempDirectory)
+        with open(destination, "wb") as img_file:
+            img_file.write(segmentation)
+        logging.info(f"{self.getCurrentTime()}: Images is stored in: {destination}")
+        return destination
+
+    def getPathToStore(self, segmentationFileName: str, tempDirectory: str) -> str:
+        return tempDirectory + "/" + segmentationFileName
+
+    def displaySegmention(self, destination: str):
+        """
+        Displays the segmentation in slicer window
+        """
+        segmentation = slicer.util.loadSegmentation(destination)
+
+    def requestDicomImage(self, image_id: str, image_name: str, node_name: str, checksum: str):
+        download_uri = self.imageDataController.getDicomDownloadUri(image_id)
+        sampleDataLogic = SampleData.SampleDataLogic()
+        _volumeNode = sampleDataLogic.downloadFromURL(
+            nodeNames=node_name, fileNames=image_name, uris=download_uri, checksums=checksum
+        )[0]
+
+    def setTempFolderDir(self):
+        """
+        Create temporary dirctory to store the downloaded segmentation (.nrrd)
+        """
+        if self.temp_dir is None:
+            self.temp_dir = tempfile.TemporaryDirectory()
+        logging.info(f"{self.getCurrentTime()}: Temporary Directory: '{self.temp_dir.name}'")
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MonaiServerREST.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MonaiServerREST.py
new file mode 100644
index 000000000..3d6e11c94
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/MonaiServerREST.py
@@ -0,0 +1,118 @@
+import datetime
+import logging
+from urllib.parse import quote_plus
+
+import requests
+from requests.structures import CaseInsensitiveDict
+
+"""
+MonaiServerREST provides the REST endpoints to the MONAIServer
+"""
+
+
+class MonaiServerREST:
+    def __init__(self, serverUrl: str):
+        self.serverUrl = serverUrl
+
+    def getServerUrl(self) -> str:
+        return self.serverUrl
+
+    def getCurrentTime(self) -> datetime:
+        return datetime.datetime.now()
+
+    def requestDataStoreInfo(self) -> dict:
+        download_uri = f"{self.serverUrl}/datastore/?output=all"
+
+        try:
+            response = requests.get(download_uri, timeout=5)
+        except Exception as exception:
+            logging.warning(f"{self.getCurrentTime()}: Request for DataStoreInfo failed due to '{exception}'")
+            return None
+        if response.status_code != 200:
+            logging.warning(
+                "{}: Request for datastore-info failed (url: '{}'). Response code is {}".format(
+                    self.getCurrentTime(), download_uri, response.status_code
+                )
+            )
+            return None
+
+        return response.json()
+
+    def getDicomDownloadUri(self, image_id: str) -> str:
+        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 requestSegmentation(self, image_id: str, tag="final") -> requests.models.Response:
+        download_uri = f"{self.serverUrl }/datastore/label?label={quote_plus(image_id)}&tag={quote_plus(tag)}"
+        logging.info(f"{self.getCurrentTime()}: REST: request segmentation '{download_uri}'")
+
+        try:
+            response = requests.get(download_uri, timeout=5)
+        except Exception as exception:
+            logging.warning(
+                "{}: Segmentation request (image id: '{}') failed due to '{}'".format(
+                    self.getCurrentTime(), image_id, exception
+                )
+            )
+            return None
+        if response.status_code != 200:
+            logging.warn(
+                "{}: Segmentation request (image id: '{}') failed due to response code: '{}'".format(
+                    self.getCurrentTime(), image_id, response.status_code
+                )
+            )
+            return None
+
+        return response
+
+    def checkServerConnection(self) -> bool:
+        if not self.serverUrl:
+            self.serverUrl = "http://127.0.0.1:8000"
+        url = self.serverUrl.rstrip("/")
+
+        try:
+            response = requests.get(url, timeout=5)
+        except Exception as exception:
+            logging.warning(f"{self.getCurrentTime()}: Connection to Monai Server failed due to '{exception}'")
+            return False
+        if response.status_code != 200:
+            logging.warn(
+                "{}: Server connection Failed. (response code = {}) ".format(
+                    self.getCurrentTime(), response.status_code
+                )
+            )
+            return False
+
+        logging.info(f"{self.getCurrentTime()}: Successfully connected to server (server url: '{url}').")
+        return True
+
+    def updateLabelInfo(self, image_id: str, params: str) -> int:
+        """
+        the image_id is the unique ID of an radiographic image
+        If the image has a label/segmentation, its label/label_id corresponds to its image_id
+        """
+        url = f"{self.serverUrl}/datastore/updatelabelinfo?label={quote_plus(image_id)}"
+        headers = CaseInsensitiveDict()
+        headers["Content-Type"] = "application/x-www-form-urlencoded"
+        headers["accept"] = "application/json"
+
+        try:
+            response = requests.put(url, headers=headers, data=params)
+        except Exception as exception:
+            logging.warning(
+                "{}: Update meta data (image id: '{}') failed due to '{}'".format(
+                    self.getCurrentTime(), image_id, exception
+                )
+            )
+            return None
+        if response.status_code != 200:
+            logging.warn(
+                "{}: Update meta data (image id: '{}') failed due to response code = {}) ".format(
+                    self.getCurrentTime(), image_id, response.status_code
+                )
+            )
+            return response.status_code
+
+        logging.info(f"{self.getCurrentTime()}: Meta data was updated successfully (image id: '{image_id}').")
+        return response.status_code
diff --git a/plugins/slicer/MONAILabelReviewer/ReviewerLibs/SegmentationMeta.py b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/SegmentationMeta.py
new file mode 100644
index 000000000..a5ab9c8f0
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/ReviewerLibs/SegmentationMeta.py
@@ -0,0 +1,85 @@
+import json
+import time
+
+"""
+SegmentationMeta stores all the meta data of its corresponding ImageData
+The class returns a json string which will be send to MONAI-Server to persist the
+information in datastore.json
+"""
+
+
+class SegmentationMeta:
+    def __init__(self):
+        self.preFix = "params="
+        self.status: str = ""
+        self.level: str = ""
+        self.approvedBy: str = ""
+        self.editTime: str = ""
+        self.comment: str = ""
+
+    def build(self, status="", level="", approvedBy="", comment=""):
+        self.setEditTime()
+        self.status = status
+        self.level = level
+        self.approvedBy = approvedBy
+        self.comment = comment
+
+    def setApprovedBy(self, approvedBy: str):
+        self.setEditTime()
+        self.approvedBy = approvedBy
+
+    def setStatus(self, status: str):
+        self.setEditTime()
+        self.status = status
+
+    def setLevel(self, level: str):
+        self.setEditTime()
+        self.level = level
+
+    def setComment(self, comment: str):
+        self.setEditTime()
+        self.comment = comment
+
+    def setEditTime(self):
+        self.editTime = str(time.ctime())
+
+    def getMeta(self) -> str:
+        self.setEditTime()
+        metaJson = {
+            "segmentationMeta": {
+                "status": self.status,
+                "approvedBy": self.approvedBy,
+                "level": self.level,
+                "comment": self.comment,
+                "editTime": self.editTime,
+            }
+        }
+        jsonStr = json.dumps(metaJson)
+        return self.preFix + jsonStr
+
+    def getStatus(self) -> str:
+        return self.status
+
+    def getLevel(self) -> str:
+        return self.level
+
+    def getApprovedBy(self) -> str:
+        return self.approvedBy
+
+    def isEqual(self, status="", level="", approvedBy="", comment=""):
+        if status != self.status:
+            return False
+        if approvedBy != self.approvedBy:
+            return False
+        if level != self.level:
+            return False
+        if comment != self.comment:
+            return False
+        return True
+
+    def display(self):
+        print("status: ", self.status)
+        print("level: ", self.level)
+        print("approvedBy: ", self.approvedBy)
+        print("editTime: ", self.editTime)
+        print("comment: ", self.comment)
diff --git a/plugins/slicer/MONAILabelReviewer/Testing/CMakeLists.txt b/plugins/slicer/MONAILabelReviewer/Testing/CMakeLists.txt
new file mode 100644
index 000000000..655007a0a
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/Testing/CMakeLists.txt
@@ -0,0 +1,1 @@
+add_subdirectory(Python)
diff --git a/plugins/slicer/MONAILabelReviewer/Testing/Python/CMakeLists.txt b/plugins/slicer/MONAILabelReviewer/Testing/Python/CMakeLists.txt
new file mode 100644
index 000000000..5658d8bca
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/Testing/Python/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+#slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py)
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataControllerTest.py b/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataControllerTest.py
new file mode 100644
index 000000000..ca3898dee
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataControllerTest.py
@@ -0,0 +1,389 @@
+import json
+import os
+import sys
+import unittest
+from typing import Dict
+from unittest.mock import Mock, patch
+
+sys.path.append("..")
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.ImageDataController import ImageDataController
+from ReviewerLibs.MONAILabelReviewerEnum import Level, SegStatus
+from ReviewerLibs.MonaiServerREST import MonaiServerREST
+
+
+class ImageDataControllerTest(unittest.TestCase):
+    @classmethod
+    def setUp(self):
+        self.STATUS = SegStatus()
+        self.LEVEL = Level()
+        self.url = "http://127.0.0.1:8000"
+        self.controller = ImageDataController()
+
+        self.nameToImageData: Dict[str, ImageData] = {}
+        self.createImageData()
+        self.testDataStore_V2_json = ""
+        self.loadJsonStr()
+
+    @classmethod
+    def loadJsonStr(self) -> str:
+        with open(os.path.join(sys.path[0], "TestDataSet/test_json_datastore_v2.json")) as f:
+            self.testDataStore_V2_json = json.dumps(json.load(f))
+
+    @classmethod
+    def createImageData(self):
+
+        # is segmented
+        imageDataTest_1 = ImageData(
+            name="6667571",
+            fileName="6667571.dcm",
+            nodeName="6667571.dcm",
+            checkSum="SHA256:2a454e9ab8a33dc74996784163a362a53e04adcee2fd73a8b6299bf0ce5060d3",
+            segmented=True,
+            timeStamp=1639985938,
+            comment="",
+        )
+        imageDataTest_1.setClientId("Test-Radiolgist-Segmented")
+        imageDataTest_1.setSegmentationFileName("6667571.seg.nrrd")
+        imageDataTest_1.setSegmentationMeta(
+            status=self.STATUS.APPROVED, level=self.LEVEL.HARD, approvedBy="Test-Reviewer", comment=""
+        )
+
+        # is segmented
+        imageDataTest_2 = ImageData(
+            name="imageId_2",
+            fileName="fileName_2",
+            nodeName="nodeName_2",
+            checkSum="checkSum_2",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_2",
+        )
+        imageDataTest_2.setClientId("client_id_1")
+        imageDataTest_2.setSegmentationFileName("testSegementation_2.nrrd")
+        imageDataTest_2.setSegmentationMeta(
+            status=self.STATUS.FLAGGED, level=self.LEVEL.MEDIUM, approvedBy="theRadologist_2", comment="comment_2"
+        )
+
+        # is not segmented
+        imageDataTest_3 = ImageData(
+            name="6213798",
+            fileName="6213798.dcm",
+            nodeName="6213798.dcm",
+            checkSum="SHA256:5ca275af76a8fe88939058f9c91ecb72ce96bd5f012198fc366e4ef0214849b9",
+            segmented=False,
+            timeStamp=1642170835,
+            comment="",
+        )
+
+        self.nameToImageData["6667571"] = imageDataTest_1
+        self.nameToImageData["imageId_2"] = imageDataTest_2
+        self.nameToImageData["6213798"] = imageDataTest_3
+
+    @classmethod
+    def areEqual(self, imageData_1: ImageData, imageData_2: ImageData) -> bool:
+        if (
+            imageData_1.getClientId() == imageData_2.getClientId()
+            and imageData_1.getName() == imageData_2.getName()
+            and imageData_1.getFileName() == imageData_2.getFileName()
+            and imageData_1.getNodeName() == imageData_2.getNodeName()
+            and imageData_1.isSegemented() == imageData_2.isSegemented()
+        ):
+
+            if imageData_1.isSegemented() is False:
+                return True
+
+            if (
+                imageData_1.getSegmentationFileName() == imageData_2.getSegmentationFileName()
+                and imageData_1.isApproved() == imageData_2.isApproved()
+                and imageData_1.isFlagged() == imageData_2.isFlagged()
+                and imageData_1.getLevel() == imageData_2.getLevel()
+                and imageData_1.getApprovedBy() == imageData_2.getApprovedBy()
+            ):
+                return True
+
+        return False
+
+    @patch.object(MonaiServerREST, "getServerUrl", return_value="http://127.0.0.1:8000")
+    def test_returnedUrl(self, getServerUrl):
+        self.controller.setMonaiServer(self.url)
+        url = self.controller.getServerUrl()
+        getServerUrl.assert_called_once()
+        self.assertEqual(self.url, url)
+
+    @patch.object(MonaiServerREST, "checkServerConnection", return_value=True)
+    def test_connectToMonaiServer(self, checkServerConnection):
+        isConnected = self.controller.connectToMonaiServer(self.url)
+        checkServerConnection.assert_called_once()
+        self.assertTrue(isConnected)
+
+    def test_getMapIdToImageData(self):
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+        returnedMap = self.controller.getMapIdToImageData()
+
+        selectedImageData = returnedMap["6213798"]
+        expectedImageData = self.nameToImageData["6213798"]
+
+        self.assertTrue(self.areEqual(expectedImageData, selectedImageData))
+
+    def test_initMetaDataProcessing(self):
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+    def test_getStatistics(self):
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        statistics = self.controller.getStatistics()
+
+        self.assertEqual(50, statistics["segmentationProgress"])
+        self.assertEqual("2/4", statistics["idxTotalSegmented"])
+        self.assertEqual("1/4", statistics["idxTotalApproved"])
+        self.assertEqual(25, statistics["progressPercentage"])
+        self.assertEqual(50, statistics["segmentationProgressAllPercentage"])
+        self.assertEqual(25, statistics["approvalProgressPercentage"])
+
+    def test_getClientIds(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        clientIds = self.controller.getClientIds()
+
+        # Verify
+        self.assertListEqual(clientIds, ["Test-Radiolgist-Segmented"])
+
+    def test_getReviewers(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        clientIds = self.controller.getReviewers()
+
+        # Verify
+        self.assertListEqual(clientIds, ["Test-Reviewer"])
+
+    def test_getAllImageData(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        imageDatas = self.controller.getAllImageData(
+            segmented=True, isNotSegmented=False, isApproved=True, isFlagged=False
+        )
+
+        # Verify
+        self.assertEqual(1, len(imageDatas))
+        returnedImageData = imageDatas[0]
+        expectedImageData = self.nameToImageData["6667571"]
+        self.assertTrue(self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getImageDataByClientId(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        imageDatas = self.controller.getImageDataByClientId(
+            selectedClientId="Test-Radiolgist-Segmented", isApproved=True, isFlagged=False
+        )
+
+        # Verify
+        self.assertEqual(1, len(imageDatas))
+        returnedImageData = imageDatas[0]
+        expectedImageData = self.nameToImageData["6667571"]
+        self.assertTrue(self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getPercentageApproved(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        percentageApprovedOfClient, idxApprovedOfClient = self.controller.getPercentageApproved(
+            selectedClientId="Test-Radiolgist-Segmented"
+        )
+
+        # Verify
+        self.assertEqual(50, percentageApprovedOfClient)
+        self.assertEqual("1/2", idxApprovedOfClient)
+
+    def test_getPercentageSemgmentedByClient(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        percentageSemgmentedByClient, idxSegmentedByClient = self.controller.getPercentageSemgmentedByClient(
+            selectedClientId="Test-Radiolgist-Segmented"
+        )
+
+        # Verify
+        self.assertEqual(50, percentageSemgmentedByClient)
+        self.assertEqual("2/4", idxSegmentedByClient)
+
+    def test_getMultImageDataByIds(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        imageIdDummy = "1234567"
+        idToImage: Dict[str, ImageData] = self.controller.getMultImageDataByIds(
+            imageIds=["6213798", "6667571", imageIdDummy]
+        )
+
+        # Verify
+        self.assertTrue("6213798" in idToImage.keys())
+        self.assertTrue("6667571" in idToImage.keys())
+        self.assertTrue(imageIdDummy not in idToImage.keys())
+
+        expectedImageData_1 = self.nameToImageData["6213798"]
+        expectedImageData_2 = self.nameToImageData["6667571"]
+
+        self.assertTrue(self.areEqual(expectedImageData_1, idToImage["6213798"]))
+        self.assertTrue(self.areEqual(expectedImageData_2, idToImage["6667571"]))
+
+    def test_searchByAnnotatorReviewer_selectedReviewer_is_all(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        idToImage: Dict[str, ImageData] = self.controller.searchByAnnotatorReviewer(
+            selectedAnnotator="Test-Radiolgist-Segmented", selectedReviewer="All", isApproved=True, isFlagged=False
+        )
+
+        # Verify
+        self.assertTrue("6667571" in idToImage.keys())
+        expectedImageData = self.nameToImageData["6667571"]
+        self.assertTrue(self.areEqual(expectedImageData, idToImage["6667571"]))
+
+    def test_searchByAnnotatorReviewer_selectedReviewer_is_all_and_selectedReviewer_is_all(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        # Test
+        idToImage: Dict[str, ImageData] = self.controller.searchByAnnotatorReviewer(
+            selectedAnnotator="All", selectedReviewer="All", isApproved=True, isFlagged=False
+        )
+
+        # Verify
+        self.assertTrue("6667571" in idToImage.keys())
+        expectedImageData = self.nameToImageData["6667571"]
+        self.assertTrue(self.areEqual(expectedImageData, idToImage["6667571"]))
+
+    def test_updateLabelInfo_successfully_update_label_info(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        mockObject.updateLabelInfo = Mock(return_value=200)
+
+        # Test
+        successfullyUpdate = self.controller.updateLabelInfo(imageId="6667571", updatedMetaJson="")
+
+        # Verify
+        self.assertTrue(successfullyUpdate)
+
+    def test_updateLabelInfo_failed_update_label_info(self):
+        # Set up
+        json_with_segmentation = json.loads(self.testDataStore_V2_json)
+
+        self.controller.monaiServerREST = MonaiServerREST(self.url)
+        mockObject = self.controller.monaiServerREST
+        mockObject.requestDataStoreInfo = Mock(return_value=json_with_segmentation)
+
+        success = self.controller.initMetaDataProcessing()
+        self.assertTrue(success)
+
+        mockObject.updateLabelInfo = Mock(return_value=400)
+
+        # Test
+        failedUpdate = self.controller.updateLabelInfo(imageId="6667571", updatedMetaJson="")
+
+        # Verify
+        self.assertFalse(failedUpdate)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataExtractorTest.py b/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataExtractorTest.py
new file mode 100644
index 000000000..dc8562356
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/ImageDataExtractorTest.py
@@ -0,0 +1,333 @@
+import sys
+import unittest
+from typing import Dict, List
+
+sys.path.append("..")
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.ImageDataExtractor import ImageDataExtractor
+from ReviewerLibs.MONAILabelReviewerEnum import Level, SegStatus
+
+
+class ImageDataExtractorTest(unittest.TestCase):
+    @classmethod
+    def setUp(self):
+        self.STATUS = SegStatus()
+        self.LEVEL = Level()
+        self.createImageData()
+
+    @classmethod
+    def createImageData(self):
+
+        # is segmented
+        imageDataTest_1 = ImageData(
+            name="imageId_1",
+            fileName="fileName_1",
+            nodeName="nodeName_1",
+            checkSum="checkSum_1",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_1",
+        )
+        imageDataTest_1.setClientId("client_id_1")
+        imageDataTest_1.setSegmentationFileName("testSegementation_1.nrrd")
+        imageDataTest_1.setSegmentationMeta(
+            status=self.STATUS.APPROVED, level=self.LEVEL.HARD, approvedBy="theRadologist_1", comment="comment_1"
+        )
+
+        # is segmented
+        imageDataTest_2 = ImageData(
+            name="imageId_2",
+            fileName="fileName_2",
+            nodeName="nodeName_2",
+            checkSum="checkSum_2",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_2",
+        )
+        imageDataTest_2.setClientId("client_id_1")
+        imageDataTest_2.setSegmentationFileName("testSegementation_2.nrrd")
+        imageDataTest_2.setSegmentationMeta(
+            status=self.STATUS.FLAGGED, level=self.LEVEL.MEDIUM, approvedBy="theRadologist_2", comment="comment_2"
+        )
+
+        # is not segmented
+        imageDataTest_3 = ImageData(
+            name="imageId_3",
+            fileName="fileName_3",
+            nodeName="nodeName_3",
+            checkSum="checkSum_3",
+            segmented=False,
+            timeStamp=1640171961,
+            comment="comment_3",
+        )
+        imageDataTest_3.setClientId("client_id_3")
+
+        self.nameToImageData: Dict[str, ImageData] = {}
+        self.nameToImageData["imageId_1"] = imageDataTest_1
+        self.nameToImageData["imageId_2"] = imageDataTest_2
+        self.nameToImageData["imageId_3"] = imageDataTest_3
+        self.imageDataExtractor = ImageDataExtractor(nameToImageData=self.nameToImageData)
+        self.imageDataExtractor.init()
+
+    @classmethod
+    def areEqual(self, imageData_1: ImageData, imageData_2: ImageData) -> bool:
+        if (
+            imageData_1.getClientId() == imageData_2.getClientId()
+            and imageData_1.getName() == imageData_2.getName()
+            and imageData_1.getFileName() == imageData_2.getFileName()
+            and imageData_1.getNodeName() == imageData_2.getNodeName()
+            and imageData_1.isSegemented() == imageData_2.isSegemented()
+            and imageData_1.getSegmentationFileName() == imageData_2.getSegmentationFileName()
+            and imageData_1.isApproved() == imageData_2.isApproved()
+            and imageData_1.isFlagged() == imageData_2.isFlagged()
+            and imageData_1.getLevel() == imageData_2.getLevel()
+            and imageData_1.getApprovedBy() == imageData_2.getApprovedBy()
+        ):
+            return True
+        return False
+
+    def test_getTotalNumImages(self):
+        totalNumOfImages = self.imageDataExtractor.getTotalNumImages()
+        print(totalNumOfImages)
+        self.assertEqual(len(self.nameToImageData), totalNumOfImages)
+
+    def test_getImageDataIds(self):
+        ids = self.imageDataExtractor.getImageDataIds()
+        expectedIds = [*self.nameToImageData.keys()]
+        containsAll = all(id in ids for id in expectedIds)
+        self.assertEqual(True, containsAll)
+
+    def test_getClientIds(self):
+
+        expectedClientIds = ["client_id_1"]
+        clientIds = self.imageDataExtractor.getClientIds()
+        self.assertEqual(len(expectedClientIds), len(clientIds))
+        containsClients = all(id in clientIds for id in expectedClientIds)
+        self.assertEqual(True, containsClients)
+
+    def test_getReviewers(self):
+        expectedReviewersIds = ["theRadologist_1", "theRadologist_2"]
+        reviewerIds = self.imageDataExtractor.getReviewers()
+        self.assertEqual(len(expectedReviewersIds), len(reviewerIds))
+        containsReviewers = all(id in reviewerIds for id in expectedReviewersIds)
+        self.assertEqual(True, containsReviewers)
+
+    def test_getImageDataNotsegmented(self):
+        notSegmentedImages: List[ImageData] = self.imageDataExtractor.getImageDataNotsegmented()
+        self.assertEqual(1, len(notSegmentedImages))
+        notSegementedImage = notSegmentedImages[0]
+        self.assertEqual("client_id_3", notSegementedImage.getClientId())
+        self.assertEqual(False, notSegementedImage.isSegemented())
+
+    def test_getNumOfNotSegmented(self):
+        numOfNotSegmented = self.imageDataExtractor.getNumOfNotSegmented()
+        self.assertEqual(1, numOfNotSegmented)
+
+    def test_getNumOfSegmented(self):
+        numOfNotSegmented = self.imageDataExtractor.getNumOfSegmented()
+        self.assertEqual(2, numOfNotSegmented)
+
+    def test_getSegmentationProgessInPercentage(self):
+        percentage = self.imageDataExtractor.getSegmentationProgessInPercentage()
+        self.assertEqual(66, percentage)
+
+    def test_getSegmentationProgessInPercentage_as_fraction(self):
+        idxTotalSegmented = self.imageDataExtractor.getSegmentationVsTotalStr()
+        self.assertEqual("2/3", idxTotalSegmented)
+
+    def test_getApprovalProgressInPercentage(self):
+        fraction = self.imageDataExtractor.getApprovalProgressInPercentage()
+        self.assertEqual(33, fraction)
+
+    def test_getApprovalVsTotal(self):
+        idxTotalApproved = self.imageDataExtractor.getApprovalVsTotal()
+        self.assertEqual("1/3", idxTotalApproved)
+
+    def test_getAllImageData_segmented_is_true_approved_is_true(self):
+        imageDatas = self.imageDataExtractor.getAllImageData(
+            segmented=True, notSegmented=False, approved=True, flagged=False
+        )
+        self.assertEqual(1, len(imageDatas))
+        expectedImageData = self.nameToImageData["imageId_1"]
+        self.assertEqual(True, self.areEqual(expectedImageData, imageDatas[0]))
+
+    def test_getAllImageData_segmented_is_true_flagges_is_true(self):
+        imageDatas = self.imageDataExtractor.getAllImageData(
+            segmented=True, notSegmented=False, approved=False, flagged=True
+        )
+        self.assertEqual(1, len(imageDatas))
+        expectedImageData = self.nameToImageData["imageId_2"]
+        self.assertEqual(True, self.areEqual(expectedImageData, imageDatas[0]))
+
+    def test_getAllImageData_isNotSegmented_is_false_approved_is_false(self):
+        imageDatas = self.imageDataExtractor.getAllImageData(
+            segmented=False, notSegmented=True, approved=False, flagged=False
+        )
+        self.assertEqual(1, len(imageDatas))
+        expectedImageData = self.nameToImageData["imageId_3"]
+        self.assertEqual(True, self.areEqual(expectedImageData, imageDatas[0]))
+
+    def test_getImageDataByClientId_approved_is_true(self):
+
+        imageDataTest_4 = ImageData(
+            name="imageId_4",
+            fileName="fileName_4",
+            nodeName="nodeName_4",
+            checkSum="checkSum_4",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_4",
+        )
+        imageDataTest_4.setClientId("client_id_1")
+        imageDataTest_4.setSegmentationFileName("testSegementation_4.nrrd")
+        imageDataTest_4.setSegmentationMeta(
+            status=self.STATUS.APPROVED, level=self.LEVEL.MEDIUM, approvedBy="theRadologist_4", comment="comment_4"
+        )
+        self.nameToImageData["imageId_4"] = imageDataTest_4
+
+        imageDataExtractor = ImageDataExtractor(nameToImageData=self.nameToImageData)
+        imageDataExtractor.init()
+        returnedImageDatas = imageDataExtractor.getImageDataByClientId(
+            clientId="client_id_1", approved=True, flagged=False
+        )
+        self.assertEqual(2, len(returnedImageDatas))
+        returnedImageData_1 = list(filter(lambda image: (image.getName() == "imageId_1"), returnedImageDatas))
+        returnedImageData_4 = list(filter(lambda image: (image.getName() == "imageId_4"), returnedImageDatas))
+
+        self.assertEqual(True, self.areEqual(self.nameToImageData["imageId_1"], returnedImageData_1[0]))
+        self.assertEqual(True, self.areEqual(self.nameToImageData["imageId_4"], returnedImageData_4[0]))
+
+    def test_getImageDataByClientAndReviewer_approved_is_true(self):
+        imageDataExtractor = ImageDataExtractor(nameToImageData=self.nameToImageData)
+        imageDataExtractor.init()
+        returnedImageDatas = imageDataExtractor.getImageDataByClientAndReviewer(
+            clientId="client_id_1", reviewerId="theRadologist_1", approved=True, flagged=False
+        )
+        for imageData in returnedImageDatas:
+            imageData.display()
+
+        self.assertEqual(1, len(returnedImageDatas))
+        imageData = returnedImageDatas[0]
+        self.assertEqual(True, self.areEqual(self.nameToImageData["imageId_1"], imageData))
+
+    def test_getImageDataByClientId_flagged_is_true(self):
+        returnedImageDatas = self.imageDataExtractor.getImageDataByClientId(
+            clientId="client_id_1", approved=False, flagged=True
+        )
+        self.assertEqual(1, len(returnedImageDatas))
+        returnedImageData = returnedImageDatas[0]
+        expectedImageData = self.nameToImageData["imageId_2"]
+        self.assertEqual(True, self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getImageDataByReviewer_approved_is_true(self):
+        returnedImageDatas = self.imageDataExtractor.getImageDataByReviewer(
+            reviewerId="theRadologist_1", approved=True, flagged=False
+        )
+        self.assertEqual(1, len(returnedImageDatas))
+        returnedImageData = returnedImageDatas[0]
+        expectedImageData = self.nameToImageData["imageId_1"]
+        self.assertEqual(True, self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getImageDataByReviewer_flagged_is_true(self):
+        returnedImageDatas = self.imageDataExtractor.getImageDataByReviewer(
+            reviewerId="theRadologist_2", approved=False, flagged=True
+        )
+        self.assertEqual(1, len(returnedImageDatas))
+        returnedImageData = returnedImageDatas[0]
+        expectedImageData = self.nameToImageData["imageId_2"]
+        self.assertEqual(True, self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getImageDataByLevel_level_hard(self):
+        returnedImageDatas = self.imageDataExtractor.getImageDataByLevel(isEasy=False, isMedium=False, isHard=True)
+        self.assertEqual(1, len(returnedImageDatas))
+        returnedImageData = returnedImageDatas["imageId_1"]
+        expectedImageData = self.nameToImageData["imageId_1"]
+        self.assertEqual(True, self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getSingleImageDataById_found_ImageData(self):
+        returnedImageData = self.imageDataExtractor.getSingleImageDataById(imageId="imageId_1")
+        expectedImageData = self.nameToImageData["imageId_1"]
+        self.assertTrue(self.areEqual(expectedImageData, returnedImageData))
+
+    def test_getSingleImageDataById_isBlank(self):
+        returnedImageData = self.imageDataExtractor.getSingleImageDataById(imageId=" ")
+        self.assertIsNone(returnedImageData)
+
+    def test_getMultImageDataByIds(self):
+        returnedImageDatas: Dict[str, ImageData] = self.imageDataExtractor.getMultImageDataByIds(
+            ids=["imageId_1", "imageId_3", "dummy"]
+        )
+        self.assertEqual(2, len(returnedImageDatas))
+
+        imageDataWithimageId_1 = returnedImageDatas["imageId_1"]
+        imageDataWithimageId_3 = returnedImageDatas["imageId_3"]
+        self.assertTrue(self.areEqual(self.nameToImageData["imageId_1"], imageDataWithimageId_1))
+        self.assertTrue(self.areEqual(self.nameToImageData["imageId_3"], imageDataWithimageId_3))
+
+    def test_getMultImageDataByIds_given_empty_idList(self):
+        returnedImageDatas: Dict[str, ImageData] = self.imageDataExtractor.getMultImageDataByIds(ids=[])
+        self.assertEqual(0, len(returnedImageDatas))
+
+    def test_getNumApprovedSegmentation(self):
+        numApprovedSegmentation = self.imageDataExtractor.getNumApprovedSegmentation()
+        self.assertEqual(1, numApprovedSegmentation)
+
+    def test_getPercentageApproved(self):
+        imageDataTest_4 = ImageData(
+            name="imageId_4",
+            fileName="fileName_4",
+            nodeName="nodeName_4",
+            checkSum="checkSum_4",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_4",
+        )
+        imageDataTest_4.setClientId("client_id_1")
+        imageDataTest_4.setSegmentationFileName("testSegementation_4.nrrd")
+        imageDataTest_4.setSegmentationMeta(
+            status=self.STATUS.APPROVED, level=self.LEVEL.MEDIUM, approvedBy="theRadologist_4", comment="comment_4"
+        )
+        self.nameToImageData["imageId_4"] = imageDataTest_4
+
+        imageDataExtractor = ImageDataExtractor(nameToImageData=self.nameToImageData)
+        imageDataExtractor.init()
+
+        precentage, idxApprovedOfClient = imageDataExtractor.getPercentageApproved(clientId="client_id_1")
+        self.assertEqual(66, precentage)
+        self.assertEqual("2/3", idxApprovedOfClient)
+
+    def test_getPercentageSemgmentedByClient(self):
+        imageDataTest_4 = ImageData(
+            name="imageId_4",
+            fileName="fileName_4",
+            nodeName="nodeName_4",
+            checkSum="checkSum_4",
+            segmented=True,
+            timeStamp=1640171961,
+            comment="comment_4",
+        )
+        imageDataTest_4.setClientId("client_id_1")
+        imageDataTest_4.setSegmentationFileName("testSegementation_4.nrrd")
+        imageDataTest_4.setSegmentationMeta(
+            status=self.STATUS.APPROVED, level=self.LEVEL.MEDIUM, approvedBy="theRadologist_4", comment="comment_4"
+        )
+        self.nameToImageData["imageId_4"] = imageDataTest_4
+
+        imageDataExtractor = ImageDataExtractor(nameToImageData=self.nameToImageData)
+        imageDataExtractor.init()
+
+        precentage, idxSegmentedByClien = imageDataExtractor.getPercentageSemgmentedByClient(clientId="client_id_1")
+        self.assertEqual(75, precentage)
+        self.assertEqual("3/4", idxSegmentedByClien)
+
+    def test_getApprovedSegmentationIds(self):
+        idsOfApprovedSementations = self.imageDataExtractor.getApprovedSegmentationIds()
+        self.assertEqual(1, len(idsOfApprovedSementations))
+
+    def test_getSegmentedImageIds(self):
+        idsOfSegmented = self.imageDataExtractor.getSegmentedImageIds()
+        self.assertEqual(2, len(idsOfSegmented))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/JsonParserTest.py b/plugins/slicer/MONAILabelReviewer/UnitTests/JsonParserTest.py
new file mode 100644
index 000000000..e8cb18b4b
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/JsonParserTest.py
@@ -0,0 +1,109 @@
+import json
+import os
+import sys
+import unittest
+
+sys.path.append("..")
+from ReviewerLibs.ImageData import ImageData
+from ReviewerLibs.JsonParser import JsonParser
+
+
+class JsonParserTest(unittest.TestCase):
+    @classmethod
+    def setUp(self):
+        self.jsonParser = JsonParser(None)
+
+        fileNameWithOutSegmentation = "test_datastore_v2_image_without_segmentation.json"
+        json_without_segmentation_str = self.loadJsonStr(fileNameWithOutSegmentation)
+
+        self.json_without_segmentation = json.loads(json_without_segmentation_str)
+
+        fileNameWithSegmentation = "test_datastore_v2_image_with_segmentation.json"
+        json_with_segmentation_str = self.loadJsonStr(fileNameWithSegmentation)
+        self.json_with_segmentation = json.loads(json_with_segmentation_str)
+
+    @classmethod
+    def loadJsonStr(self, fileName: str) -> str:
+        with open(os.path.join(sys.path[0], "TestDataSet/" + fileName)) as f:
+            data = json.dumps(json.load(f))
+        return data
+
+    def test_getFileName(self):
+        name = self.jsonParser.getFileName(self.json_without_segmentation)
+        self.assertEqual(name, "6245968.dcm")
+
+    def test_getNodeName(self):
+        name = self.jsonParser.getNodeName(self.json_without_segmentation)
+        self.assertEqual(name, "6245968.dcm")
+
+    def test_getCheckSum(self):
+        name = self.jsonParser.getCheckSum(self.json_without_segmentation)
+        self.assertEqual(name, "SHA256:f1f8ef13433b1f0966e589818f3180750606eff69b3bc4a55e0181d7a9da8da1")
+
+    def test_getTimeStamp(self):
+        name = self.jsonParser.getTimeStamp(self.json_without_segmentation)
+        self.assertEqual(name, 1642371057)
+
+    def test_getInfo(self):
+        name = self.jsonParser.getInfo(self.json_without_segmentation)
+        info = '{"ts": 1642170799, "checksum": "SHA256:f1f8ef13433b1f0966e589818f3180750606eff69b3bc4a55e0181d7a9da8da1", "name": "6245968.dcm", "strategy": {"Random": {"ts": 1642371057, "client_id": "Dr Radiologist"}}}'
+        infoDict = json.loads(info)
+        self.assertEqual(name, infoDict)
+
+    def test_isSegmented(self):
+        name = self.jsonParser.isSegmented(self.json_without_segmentation)
+        self.assertEqual(False, name)
+
+    def test_getSegmentationName(self):
+        name = self.jsonParser.getSegmentationName(self.json_without_segmentation)
+        self.assertEqual(name, "6245968.seg.nrrd")
+
+    def test_hasKeyAnnotate(self):
+        result = self.jsonParser.hasKeyAnnotate(self.json_without_segmentation)
+        self.assertEqual(result, False)
+
+    def test_hasKeyRandom(self):
+        result = self.jsonParser.hasKeyRandom(self.json_without_segmentation)
+        self.assertEqual(result, True)
+
+    def test_getClientId(self):
+        result = self.jsonParser.getClientId(self.json_without_segmentation)
+        self.assertEqual(result, "Dr Radiologist")
+
+    def test_getMetaStatus(self):
+        result = self.jsonParser.getMetaStatus("final", self.json_with_segmentation)
+        self.assertEqual(result, "flagged")
+
+    def test_getMetaLevel(self):
+        result = self.jsonParser.getMetaLevel("final", self.json_with_segmentation)
+        self.assertEqual(result, "easy")
+
+    def test_getMetaApprovedBy(self):
+        result = self.jsonParser.getMetaApprovedBy("final", self.json_with_segmentation)
+        self.assertEqual(result, "Prof Radiogolist")
+
+    def test_getMetaEditTime(self):
+        result = self.jsonParser.getMetaEditTime("final", self.json_with_segmentation)
+        self.assertEqual(result, "Thu Jan 13 12:21:01 2022")
+
+    def test_getMetaComment(self):
+        result = self.jsonParser.getMetaComment("final", self.json_with_segmentation)
+        self.assertEqual(result, "Segementation was not easy")
+
+    def test_jsonToImageData(self):
+        imageData: ImageData = self.jsonParser.jsonToImageData("6662775.dcm", self.json_with_segmentation)
+        self.assertEqual(imageData.getName(), "6662775.dcm")
+        self.assertEqual(imageData.getFileName(), "6662775.dcm")
+        self.assertEqual(
+            imageData.getCheckSum(), "SHA256:1b474d23bda3de0c28f4287a7c0380d461e9f71d0dc468e64f336412cb575327"
+        )
+        self.assertEqual(imageData.isSegemented(), True)
+        self.assertEqual(imageData.getClientId(), "Annotator")
+        self.assertEqual(imageData.getStatus(), "flagged")
+        self.assertEqual(imageData.getLevel(), "easy")
+        self.assertEqual(imageData.getApprovedBy(), "Prof Radiogolist")
+        self.assertEqual(imageData.getTime(), "2021-12-16 10:35:51")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/MonaiServerRESTTest.py b/plugins/slicer/MONAILabelReviewer/UnitTests/MonaiServerRESTTest.py
new file mode 100644
index 000000000..812bdd3e7
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/MonaiServerRESTTest.py
@@ -0,0 +1,136 @@
+import json
+import logging
+import os
+import sys
+import time
+import unittest
+
+import requests
+from mockserver_friendly import MockServerFriendlyClient, json_equals, request, response, times
+from python_on_whales import docker
+
+sys.path.append("..")
+from ReviewerLibs.MonaiServerREST import MonaiServerREST
+
+
+class MonaiServerRESTTest(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls) -> None:
+        logging.info("Start test sets of MonaiServerREST-Test-class")
+        cls.MOCK_SERVER_URL = "http://localhost:1080"
+        cls.strJson = cls.loadJsonStr("test_json_datastore_v2.json")
+        cls.mockServerContainer = docker.run(
+            "jamesdbloom/mockserver:mockserver-5.4.1", publish=[(1080, 1080)], detach=True
+        )
+        if cls.mockServerContainer.state.running is True:
+            logging.info(f"Mock server started successfully. Listening on port {1080}")
+        else:
+            logging.info("Starting mockserver failed")
+        time.sleep(8)
+        return super().setUpClass()
+
+    @classmethod
+    def tearDownClass(cls) -> None:
+        cls.mockServerContainer.stop()
+        if cls.mockServerContainer.state.running is False:
+            logging.info("Mock server terminated successfully")
+        cls.mockServerContainer.remove()
+        return super().tearDownClass()
+
+    @classmethod
+    def loadJsonStr(cls, fileName: str) -> str:
+        with open(os.path.join(sys.path[0], "TestDataSet/" + fileName)) as f:
+            data = json.dumps(json.load(f))
+        return data
+
+    def test_requestDataStoreInfo(cls):
+
+        client = MockServerFriendlyClient(cls.MOCK_SERVER_URL)
+        urlToBeTested = "http://localhost:1080/datastore?output=all"
+        client.expect(
+            request(method="GET", path="/datastore", querystring={"output": "all"}, body=json_equals(cls.strJson)),
+            response(code=200),
+            times(1),
+        )
+
+        result = requests.get(
+            cls.MOCK_SERVER_URL + "/datastore",
+            params={"output": "all"},
+            json=cls.strJson,
+            headers={"Content-Type": "application/json"},
+        )
+
+        cls.assertEqual(200, result.status_code)
+        cls.assertEqual(urlToBeTested, result.url)
+        client.reset()
+
+    def test_checkServerConnection_request_for_image_with_segmentation_meta_data(cls):
+        client = MockServerFriendlyClient(cls.MOCK_SERVER_URL)
+
+        urlToBeTested = cls.MOCK_SERVER_URL + "/datastore/updatelabelinfo?label=6662775"
+        body = {
+            "segmentationMeta": {
+                "status": "approved",
+                "approvedBy": "",
+                "level": "hard",
+                "comment": "",
+                "editTime": "Fri May 27 07:41:08 2022",
+            }
+        }
+
+        client.expect(
+            request(
+                method="PUT",
+                path="/datastore/updatelabelinfo",
+                querystring={"image": "6662775"},
+                headers={"content-Type": "application/json"},
+                body=json_equals(body),
+            ),
+            response(code=200),
+            times(1),
+        )
+
+        result = requests.put(
+            cls.MOCK_SERVER_URL + "/datastore/updatelabelinfo",
+            params={"image": "6662775"},
+            json=body,
+            headers={"content-Type": "application/json"},
+        )
+
+        cls.assertEqual(200, result.status_code)
+        cls.assertEqual(urlToBeTested, result.url)
+        client.reset()
+
+    def test_checkServerConnection(cls):
+        client = MockServerFriendlyClient(cls.MOCK_SERVER_URL)
+        client.expect(request(method="GET"), response(code=200), times(1))
+
+        result = requests.get(cls.MOCK_SERVER_URL)
+
+        cls.assertEqual(200, result.status_code)
+        cls.assertEqual(cls.MOCK_SERVER_URL + "/", result.url)
+
+    def test_requestSegmentation(cls):
+        urlToBeTested = cls.MOCK_SERVER_URL + "/datastore/label?label=6662775&tag=final"
+        client = MockServerFriendlyClient(cls.MOCK_SERVER_URL)
+
+        client.expect(
+            request(method="GET", path="/datastore/label", querystring={"label": "6662775", "tag": "final"}),
+            response(code=200),
+            times(1),
+        )
+
+        result = requests.get(cls.MOCK_SERVER_URL + "/datastore/label", params={"label": "6662775", "tag": "final"})
+
+        cls.assertEqual(200, result.status_code)
+        cls.assertEqual(urlToBeTested, result.url)
+
+    def test_getDicomDownloadUri(cls):
+        monaiServerREST = MonaiServerREST(cls.MOCK_SERVER_URL)
+        imageId = "6662775"
+        url = monaiServerREST.getDicomDownloadUri(imageId)
+        cls.assertEqual("http://localhost:1080/datastore/image?image=6662775", url)
+
+
+if __name__ == "__main__":
+    unittest.main()


Test patch:
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_with_segmentation.json b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_with_segmentation.json
new file mode 100644
index 000000000..5f73cfb45
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_with_segmentation.json
@@ -0,0 +1,56 @@
+{
+  "image": {
+    "ext": ".dcm",
+    "info": {
+      "ts": 1639519453,
+      "checksum": "SHA256:1b474d23bda3de0c28f4287a7c0380d461e9f71d0dc468e64f336412cb575327",
+      "name": "6662775.dcm",
+      "strategy": {
+        "annotate": {
+          "ts": 1639647351,
+          "client_id": "Annotator"
+        }
+      }
+    }
+  },
+  "labels": {
+    "final": {
+      "ext": ".seg.nrrd",
+      "info": {
+        "label_info": [
+          {
+            "name": "Lung",
+            "idx": 1
+          },
+          {
+            "name": "Heart",
+            "idx": 2
+          },
+          {
+            "name": "Trachea",
+            "idx": 3
+          },
+          {
+            "name": "Mediastinum",
+            "idx": 4
+          },
+          {
+            "name": "Clavicle",
+            "idx": 5
+          }
+        ],
+        "client_id": "Annotator",
+        "ts": 1639647550,
+        "checksum": "SHA256:7563c2dda16ac2db37fdcc0587f6bbf7c3c6646dcb5676891a264388cfb1c8ba",
+        "name": "6662775.seg.nrrd",
+        "segmentationMeta": {
+          "status": "flagged",
+          "approvedBy": "Prof Radiogolist",
+          "level": "easy",
+          "comment": "Segementation was not easy",
+          "editTime": "Thu Jan 13 12:21:01 2022"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_without_segmentation.json b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_without_segmentation.json
new file mode 100644
index 000000000..33c8ec03d
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_datastore_v2_image_without_segmentation.json
@@ -0,0 +1,34 @@
+{
+  "image": {
+    "ext": ".dcm",
+    "info": {
+      "ts": 1642170799,
+      "checksum": "SHA256:f1f8ef13433b1f0966e589818f3180750606eff69b3bc4a55e0181d7a9da8da1",
+      "name": "6245968.dcm",
+      "strategy": {
+        "Random": {
+          "ts": 1642371057,
+          "client_id": "Dr Radiologist"
+        }
+      }
+    }
+  },
+  "labels": {
+    "original": {
+      "ext": ".seg.nrrd",
+      "info": {
+        "label_names": [
+          "Background",
+          "Mediastinum",
+          "Lung",
+          "Heart",
+          "Trachea",
+          "Clavicle"
+        ],
+        "ts": 1642371078,
+        "checksum": "SHA256:18f67b39871c68c7d62292b0b8bbec97dafeee66c607cf823d67303b8a59c953",
+        "name": "6245968.seg.nrrd"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_json_datastore_v2.json b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_json_datastore_v2.json
new file mode 100644
index 000000000..a7b5e6caf
--- /dev/null
+++ b/plugins/slicer/MONAILabelReviewer/UnitTests/TestDataSet/test_json_datastore_v2.json
@@ -0,0 +1,171 @@
+{
+    "name": "new-dataset",
+    "description": "New Dataset",
+    "images_dir": ".",
+    "labels_dir": "labels",
+    "objects": {
+      "6213798": {
+        "image": {
+          "ext": ".dcm",
+          "info": {
+            "ts": 1642170799,
+            "checksum": "SHA256:5ca275af76a8fe88939058f9c91ecb72ce96bd5f012198fc366e4ef0214849b9",
+            "name": "6213798.dcm",
+            "strategy": {
+              "Random": {
+                "ts": 1642170835,
+                "client_id": "Test-Radiologist-not-segmented"
+              }
+            }
+          }
+        },
+        "labels": {}
+      },
+      "6245968": {
+        "image": {
+          "ext": ".dcm",
+          "info": {
+            "ts": 1642170799,
+            "checksum": "SHA256:f1f8ef13433b1f0966e589818f3180750606eff69b3bc4a55e0181d7a9da8da1",
+            "name": "6245968.dcm",
+            "strategy": {
+              "Random": {
+                "ts": 1642371057,
+                "client_id": "Test-Radiologist"
+              }
+            }
+          }
+        },
+        "labels": {
+          "original": {
+            "ext": ".seg.nrrd",
+            "info": {
+              "label_names": [
+                "Background",
+                "Mediastinum",
+                "Lung",
+                "Heart",
+                "Trachea",
+                "Clavicle"
+              ],
+              "ts": 1642371078,
+              "checksum": "SHA256:18f67b39871c68c7d62292b0b8bbec97dafeee66c607cf823d67303b8a59c953",
+              "name": "6245968.seg.nrrd"
+            }
+          }
+        }
+      },
+      "6667571": {
+        "image": {
+          "ext": ".dcm",
+          "info": {
+            "ts": 1639519453,
+            "checksum": "SHA256:2a454e9ab8a33dc74996784163a362a53e04adcee2fd73a8b6299bf0ce5060d3",
+            "name": "6667571.dcm",
+            "strategy": {
+              "annotate": {
+                "ts": 1639985938,
+                "client_id": "Test-Radiolgist-Segmented"
+              }
+            }
+          }
+        },
+        "labels": {
+          "final": {
+            "ext": ".seg.nrrd",
+            "info": {
+              "label_info": [
+                {
+                  "name": "Lung",
+                  "idx": 1
+                },
+                {
+                  "name": "Heart",
+                  "idx": 2
+                },
+                {
+                  "name": "Trachea",
+                  "idx": 3
+                },
+                {
+                  "name": "Mediastinum",
+                  "idx": 4
+                },
+                {
+                  "name": "Clavicle",
+                  "idx": 5
+                }
+              ],
+              "client_id": "Test-Radiolgist-Segmented",
+              "ts": 1639986085,
+              "checksum": "SHA256:d58542b4ac321b137dd33edc86cd69a84aa6c1fd1c2cb704a124756ef8788e79",
+              "name": "6667571.seg.nrrd",
+              "segmentationMeta": {
+                "status": "approved",
+                "approvedBy": "Test-Reviewer",
+                "level": "hard",
+                "comment": "",
+                "editTime": "Thu Apr 28 16:56:36 2022"
+              }
+            }
+          }
+        }
+      },
+      "8887571": {
+        "image": {
+          "ext": ".dcm",
+          "info": {
+            "ts": 1639519453,
+            "checksum": "SHA256:2a454e9ab8a33dc74996784163a362a53e04adcee2fd73a8b6299bf0ce5060d3",
+            "name": "8887571.dcm",
+            "strategy": {
+              "annotate": {
+                "ts": 1639985938,
+                "client_id": "Test-Radiolgist-Segmented"
+              }
+            }
+          }
+        },
+        "labels": {
+          "final": {
+            "ext": ".seg.nrrd",
+            "info": {
+              "label_info": [
+                {
+                  "name": "Lung",
+                  "idx": 1
+                },
+                {
+                  "name": "Heart",
+                  "idx": 2
+                },
+                {
+                  "name": "Trachea",
+                  "idx": 3
+                },
+                {
+                  "name": "Mediastinum",
+                  "idx": 4
+                },
+                {
+                  "name": "Clavicle",
+                  "idx": 5
+                }
+              ],
+              "client_id": "Test-Radiolgist-Segmented",
+              "ts": 1639986085,
+              "checksum": "SHA256:d58542b4ac321b137dd33edc86cd69a84aa6c1fd1c2cb704a124756ef8788e79",
+              "name": "8887571.seg.nrrd",
+              "segmentationMeta": {
+                "status": "flagged",
+                "approvedBy": "Test-Reviewer",
+                "level": "hard",
+                "comment": "",
+                "editTime": "Thu Apr 28 16:56:36 2022"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
\ No newline at end of file