MONAILabel icon indicating copy to clipboard operation
MONAILabel copied to clipboard

Xnat datastore: Push back dicom seg to xnat

Open AHarouni opened this issue 2 years ago • 5 comments

Is your feature request related to a problem? Please describe. Xnat datastore is great. Works great. Missing feature is to implement the save method in the data store so that Admin can run batch inference on segmenation models to trigger inference, convert nifti to dicom seg then push it back to xnat

Describe the solution you'd like I have implemented these methods in the xnat.py by adding functions below

    def __convert_Nifti_2_dcmSeg(self,series_dir,niiSegPath,modelName,labelNames)->str:
        label_info =[]
        for i,lb in enumerate(labelNames):
            label_info.append(
                    {"modelName": modelName, "name": str(i+1)+'_'+lb , "description": "lb"+str(i+1)+'_'+ lb}
                        )
        dcmSegFile=nifti_to_dicom_seg(series_dir=series_dir, label=niiSegPath,label_info=label_info)
        logging.info(f" converted nifit to dicom seg --- at {dcmSegFile}")
        return dcmSegFile
##############################################################################
    def __getDcmSegUploadurl(self,image_id,aiaaModelName):
        project, subject, experiment, scan = self._id_to_fields(image_id)
        assessionID= experiment
        aiaaDcmSegUploadurlRoot = "/xapi/roi/projects/" + project + '/sessions/' + assessionID + '/collections/'
        url = "{}/xapi/roi/projects/{}/sessions/{}/collections/Pat{}_S{}_{}".format(
            self.api_url,
            quote_plus(project),
            quote_plus(assessionID),
            quote_plus(subject),
            quote_plus(scan),
            quote_plus(aiaaModelName),
        )
        return url
##############################################################################
    def save_label(self, image_id: str, label_filename: str, label_tag: str, label_info: Dict[str, Any]) -> str:
        aiaaModelName = label_info.get('model', "NoModel")  # waiting on how to get the model name
        labelNames=label_info.get('params',{}).get('label_names',{})

        #### convert nii to dcm seg and upload to Xnat
        series_dir=self._download_image(image_id)
        tmpDcmSegpath = self.__convert_Nifti_2_dcmSeg(series_dir,label_filename, aiaaModelName, labelNames)
        data = open(tmpDcmSegpath, "rb")
        url=self.__getDcmSegUploadurl(image_id,aiaaModelName)
        self._request_put(url,data)
        logger.info(f"------------- in xnat upload with {url=} and tmp file {tmpDcmSegpath=}")

        return image_id
##############################################################################
    def _request_put(self, url,data):
        logger.info(f" in upload dcm seg {url}")
        headers = {'Content-Type': "application/octet-stream"}
        params = {"overwrite": "true", "type": "SEG"}
        response = self.xnat_session.put(url, params=params, data=data, headers =headers, allow_redirects=True) # ,verify=False,
        if response.status_code != 200:  # failed call
            logger.error(f" post call error status_code= {response.status_code}  text ={response.text}")
        else:
            logger.info(f" put completed {response.text}")

        return response

Also minor modification to the nifti_to_dicom_seg function in monailabel/datastore/utils/convert.py

def nifti_to_dicom_seg(series_dir, label, label_info, file_ext="*", use_itk=True):
    start = time.time()

    label_np, meta_dict = LoadImage()(label)
    unique_labels = np.unique(label_np.flatten()).astype(np.int)
    unique_labels = unique_labels[unique_labels != 0]

    ### add model name and labels
    info = label_info[0] if label_info and 0 < len(label_info) else {}
    modelName = info.get("modelName", "AIName")

  .....

    template = {
        "ContentCreatorName": "Reader1",
        "ClinicalTrialSeriesID": "Session1",
        "ClinicalTrialTimePointID": "1",
        "SeriesDescription": modelName ,  <<<---- add model name 
        "SeriesNumber": "300",
        "InstanceNumber": "1",
        "segmentAttributes": [segment_attributes],
        "ContentLabel": "SEGMENTATION",
        "ContentDescription": "MONAI Label - Image segmentation",
        "ClinicalTrialCoordinatingCenterName": "MONAI",
        "BodyPartExamined": "",
    }

AHarouni avatar Feb 10 '23 18:02 AHarouni

Awesome.. please create a draft PR for these additional changes

SachidanandAlle avatar Feb 10 '23 18:02 SachidanandAlle

Hi Aharouni,

How did you set up the XNAT Datastore? I assume your data is stored in the XNAT and you are calling --studies http://{xnat_url}:{port}/dicom-web ?

junxiant avatar Feb 28 '23 12:02 junxiant

Unfortunately, Xnat doesn't support dicom web standard yet. That is why we created the xnat datastore which has some rest APIs to download dicoms if you have an xnat username/password.

I have xnat set up using their docker compose setup then pull in xnat-ohif plugin from using

wget -nc -q -P plugins/ https://api.bitbucket.org/2.0/repositories/icrimaginginformatics/ohif-viewer-xnat-plugin/downloads/ohif-viewer-3.4.1.jar --no-check-certificate

and place it in the plugins folder

To start monai label with xnat datastore using

export MONAI_LABEL_DATASTORE=xnat
export MONAI_LABEL_DATASTORE_USERNAME=<your xnat username default is admin>
export MONAI_LABEL_DATASTORE_PASSWORD=<your xnat password default is admin>
export MONAI_LABEL_DATASTORE_ASSET_PATH=<location of xnat data archive>
export MONAI_LABEL_DATASTORE_PROJECT=<optional >
export MONAI_LABEL_DATASTORE_CACHE_PATH=<optional local cache path ignored if MONAI_LABEL_DATASTORE_ASSET_PATH is set>
export MONAI_LABEL_DICOMWEB_CONVERT_TO_NIFTI=false

monailabel start_server --app apps/radiology --studies http://<HOST_NAME running xnat> --conf models segmentation_spleen

AHarouni avatar Feb 28 '23 18:02 AHarouni

Hi Aharouni,

Are changes required for the

def init_datastore(self) -> Datastore

function in main.py?

junxiant avatar Mar 01 '23 00:03 junxiant

Not sure what are you referring to. The xnat datastore works fine with the exports I pointed to above. I opened this issue to point out additionally functionality of triggering batch inference and pushing results back to Xnat.

AHarouni avatar Mar 01 '23 02:03 AHarouni