Beispiel #1
0
 def initStream(self, dicomDir, dicomFilePattern, dicomMinSize, **entities):
     """
     Intialize a new DicomToBids stream, watches for Dicoms and streams as BIDS
     Args:
         dicomDir: The directory where the scanner will write new DICOM files
         dicomFilePattern: A regex style pattern of the DICOM filenames to
             watch for. They should include a {TR} tag with optional formatting.
             For example filenames like '001_000013_000005.dcm' would have a
             pattern '001_000013_{TR:06d}.dcm' where the volume number (TR)
             will be filled in by a 6 digit leading zeros value.
         dicomMinSize: Minimum size of the file to return (will continue waiting
             if below this size)
         entities: BIDS entities (subject, session, task, run, suffix, datatype) that
             define the particular subject/run of the data to stream
     """
     # TODO - make sure dicomPattern has {TR} in it
     if 'subject' not in entities.keys():
         raise MissingMetadataError("Entities must include 'subject' field")
     if 'task' not in entities.keys():
         raise MissingMetadataError("Entities must include 'task' field")
     if 'suffix' not in entities.keys():
         entities['suffix'] = 'bold'
     if 'datatype' not in entities.keys():
         entities['datatype'] = 'func'
     self.entities = entities
     self.dicomDir = dicomDir
     self.dicomFilePattern = dicomFilePattern
     # TODO - restrict allowed directories, check that dicomDir is in allowed dir
     self.dataInterface = DataInterface(dataRemote=False,
                                        allowedDirs=self.allowedDirs,
                                        allowedFileTypes=['.dcm'])
     self.dicomStreamId = self.dataInterface.initScannerStream(
         dicomDir, dicomFilePattern, dicomMinSize)
     self.nextVol = 0
Beispiel #2
0
    def __init__(self, dataRemote=False, subjectRemote=False, webUI=None):
        """
        Args:
            dataRemote: whether file read/write requests will be handled directly by the projectServer
                or forwarded over websocket RPC to a remote service.
            subjectRemote: whether subject send/receive feedback will be handled locally within projectServer
                or forwarded over websocket RPC to a remote service.
        """
        self.dataRemote = dataRemote
        self.subjectRemote = subjectRemote
        allowedDirs = None
        allowedFileTypes = None
        if dataRemote is False:
            # Allow all file types and directories for local filesystem access
            allowedDirs = ['*']
            allowedFileTypes = ['*']

        # Instantiate the client service instances
        ProjectRPCService.exposed_DataInterface = DataInterface(
            dataRemote=dataRemote,
            allowedDirs=allowedDirs,
            allowedFileTypes=allowedFileTypes)
        ProjectRPCService.exposed_BidsInterface = BidsInterface(
            dataRemote=dataRemote)
        ProjectRPCService.exposed_SubjectInterface = SubjectInterface(
            subjectRemote=subjectRemote)
        ProjectRPCService.exposed_WebDisplayInterface = webUI
Beispiel #3
0
def test_readDicom():
    dicomImg1 = imgHandler.readDicomFromFile(dicomFile)
    vol1 = imgHandler.parseDicomVolume(dicomImg1, 64)
    assert vol1 is not None

    with open(dicomFile, 'rb') as fp:
        data = fp.read()
    dicomImg2 = imgHandler.readDicomFromBuffer(data)
    vol2 = imgHandler.parseDicomVolume(dicomImg2, 64)
    assert vol2 is not None
    assert (vol1 == vol2).all()

    # if dataInterface is not initialized with allowedDirs or allowedFileTypes it should fail
    dataInterface = DataInterface()
    with pytest.raises(ValidationError):
        dataInterface.initWatch(dicomDir, '*.dcm', 0)

    # Now allow all dirs and file types
    dataInterface = DataInterface(allowedDirs=['*'], allowedFileTypes=['*'])
    dataInterface.initWatch(dicomDir, '*.dcm', 0)
    dicomImg3 = imgHandler.readRetryDicomFromDataInterface(
        dataInterface, dicomFile)
    vol3 = imgHandler.parseDicomVolume(dicomImg3, 64)
    assert vol3 is not None
    assert (vol1 == vol3).all()

    # read in a truncated file, should fail and return None.
    trucatedDicomFile = os.path.join(dicomDir, test_dicomTruncFile)
    dicomImg4 = imgHandler.readRetryDicomFromDataInterface(
        dataInterface, trucatedDicomFile)
    assert dicomImg4 is None

    # Test convert to nifti
    niftiObject = dicomreaders.mosaic_to_nii(dicomImg3)
    assert niftiObject is not None

    dataInterface.fileWatcher.__del__()
    dataInterface.fileWatcher = None

    # Test anonymization of sensitive patient fields
    def countUnanonymizedSensitiveAttrs(dicomImg):
        sensitiveAttrs = 0
        for attr in imgHandler.attributesToAnonymize:
            if hasattr(dicomImg, attr) and getattr(dicomImg, attr) != "":
                sensitiveAttrs += 1
        return sensitiveAttrs

    dicomImg5 = imgHandler.readDicomFromFile(dicomFile)
    assert countUnanonymizedSensitiveAttrs(dicomImg5) >= 1

    imgHandler.anonymizeDicom(dicomImg5)
    assert countUnanonymizedSensitiveAttrs(dicomImg5) == 0
Beispiel #4
0
 def test_localDataInterface(self, dicomTestFilename, bigTestFile):
     """Test DataInterface when instantiated in local mode"""
     dataInterface = DataInterface(dataRemote=False,
                                   allowedDirs=allowedDirs,
                                   allowedFileTypes=allowedFileTypes)
     runDataInterfaceMethodTests(dataInterface, dicomTestFilename)
     runLocalFileValidationTests(dataInterface)
     runReadWriteFileTest(dataInterface, bigTestFile)
     return
Beispiel #5
0
 def __init__(self, rpyc_timeout=60, yesToPrompts=False):
     """
     Establishes an RPC connection to a localhost projectServer on a predefined port.
     The projectServer must be running on the same computer as the script using this interface.
     """
     try:
         safe_attrs = rpyc.core.protocol.DEFAULT_CONFIG.get('safe_attrs')
         safe_attrs.add('__format__')
         rpcConn = rpyc.connect(
             'localhost',
             12345,
             config={
                 "allow_public_attrs": True,
                 "safe_attrs": safe_attrs,
                 "allow_pickle": True,
                 "sync_request_timeout": rpyc_timeout,
                 # "allow_getattr": True,
                 # "allow_setattr": True,
                 # "allow_delattr": True,
                 # "allow_all_attrs": True,
             })
         # Need to provide an override class of DataInstance to return data from getImage
         self.dataInterface = WrapRpycObject(rpcConn.root.DataInterface)
         self.subjInterface = WrapRpycObject(rpcConn.root.SubjectInterface)
         self.bidsInterface = WrapRpycObject(rpcConn.root.BidsInterface)
         self.exampleInterface = WrapRpycObject(
             rpcConn.root.ExampleInterface)
         # WebDisplay is always run within the projectServer (i.e. not a remote service)
         self.webInterface = rpcConn.root.WebDisplayInterface
         self.rpcConn = rpcConn
     except ConnectionRefusedError as err:
         if yesToPrompts:
             print(
                 'Unable to connect to projectServer, continuing using localfiles'
             )
             reply = 'y'
         else:
             reply = input(
                 'Unable to connect to projectServer, continue using localfiles? '
                 + '(y/n): ')
         reply.lower().strip()
         if reply[0] == 'y':
             # These will be run in the same process as the experiment script
             self.dataInterface = DataInterface(dataRemote=False,
                                                allowedDirs=['*'],
                                                allowedFileTypes=['*'])
             self.subjInterface = SubjectInterface(subjectRemote=False)
             self.bidsInterface = BidsInterface(dataRemote=False,
                                                allowedDirs=['*'])
             self.exampleInterface = ExampleInterface(dataRemote=False)
             # Without a webServer (projectServer) the webInterface won't be able to do
             #   anything. Create a stub instance here with ioLoopInst=None so that calls
             #   to it won't thow exceptions.
             self.webInterface = WebDisplayInterface(ioLoopInst=None)
         else:
             raise err
Beispiel #6
0
    def __init__(self, args, webSocketChannelName='wsData'):
        """
        Uses the WsRemoteService framework to parse connection-related args and establish
        a connection to a remote projectServer. Instantiates a local version of
        DataInterface and BidsInterface to handle client requests coming from the
        projectServer connection.
        Args:
            args: Argparse args related to connecting to the remote server. These include
                "-s <server>", "-u <username>", "-p <password>", "--test",
                "-i <retry-connection-interval>"
            webSocketChannelName: The websocket url extension used to connecy and communicate
                to the remote projectServer, e.g. 'wsData' would connect to 'ws://server:port/wsData'
        """
        self.dataInterface = DataInterface(
            dataRemote=False,
            allowedDirs=args.allowedDirs,
            allowedFileTypes=args.allowedFileTypes)
        self.bidsInterface = BidsInterface(dataRemote=False)

        self.wsRemoteService = WsRemoteService(args, webSocketChannelName)
        self.wsRemoteService.addHandlerClass(DataInterface, self.dataInterface)
        self.wsRemoteService.addHandlerClass(BidsInterface, self.bidsInterface)
Beispiel #7
0
class DicomToBidsStream():
    """
    A class that watches for DICOM file creation in a specified directory and with
    a specified file pattern. When DICOM files arrive it converts them to BIDS
    incrementals and returns the BIDS incremental. This lets a real-time classification
    script process data directly as BIDS as it arrives from the scanner.
    """
    def __init__(self, allowedDirs=[]):
        self.allowedDirs = allowedDirs

    def initStream(self, dicomDir, dicomFilePattern, dicomMinSize, **entities):
        """
        Intialize a new DicomToBids stream, watches for Dicoms and streams as BIDS
        Args:
            dicomDir: The directory where the scanner will write new DICOM files
            dicomFilePattern: A regex style pattern of the DICOM filenames to
                watch for. They should include a {TR} tag with optional formatting.
                For example filenames like '001_000013_000005.dcm' would have a
                pattern '001_000013_{TR:06d}.dcm' where the volume number (TR)
                will be filled in by a 6 digit leading zeros value.
            dicomMinSize: Minimum size of the file to return (will continue waiting
                if below this size)
            entities: BIDS entities (subject, session, task, run, suffix, datatype) that
                define the particular subject/run of the data to stream
        """
        # TODO - make sure dicomPattern has {TR} in it
        if 'subject' not in entities.keys():
            raise MissingMetadataError("Entities must include 'subject' field")
        if 'task' not in entities.keys():
            raise MissingMetadataError("Entities must include 'task' field")
        if 'suffix' not in entities.keys():
            entities['suffix'] = 'bold'
        if 'datatype' not in entities.keys():
            entities['datatype'] = 'func'
        self.entities = entities
        self.dicomDir = dicomDir
        self.dicomFilePattern = dicomFilePattern
        # TODO - restrict allowed directories, check that dicomDir is in allowed dir
        self.dataInterface = DataInterface(dataRemote=False,
                                           allowedDirs=self.allowedDirs,
                                           allowedFileTypes=['.dcm'])
        self.dicomStreamId = self.dataInterface.initScannerStream(
            dicomDir, dicomFilePattern, dicomMinSize)
        self.nextVol = 0

    def getNumVolumes(self) -> int:
        """
        Return the number of brain volumes in the run, unknowable by this
        interface ahead of time for a real-time DICOM stream
        """
        raise NotImplementedError(
            'getNumVolumes not implemented for DicomBidsStream')

    def getIncremental(self, volIdx=-1) -> BidsIncremental:
        """
        Get the BIDS incremental for the corresponding DICOM image indicated
            by the volIdx, where volIdx is equivalent to TR id.
        VolIdx acts similar to a file_seek pointer. If a volIdx >= 0 is supplied
            the volume pointer is advanced to that position. If no volIdx or
            a volIdx < 0 is supplied, then the next image volume after the previous
            position is returned and the pointer is incremented.
        Args:
            volIdx: The volume index (or TR) within the run to retrieve.
        Returns:
            BidsIncremental for the matched DICOM for the run/volume
        """
        if volIdx >= 0:
            # reset the next volume to the user specified volume
            self.nextVol = volIdx
        else:
            # use the default next volume
            pass
        # wait for the dicom and create a bidsIncremental
        dcmImg = self.dataInterface.getImageData(self.dicomStreamId,
                                                 self.nextVol)
        dicomMetadata = getDicomMetadata(dcmImg)
        dicomMetadata.update(self.entities)
        niftiImage = convertDicomImgToNifti(dcmImg)
        incremental = BidsIncremental(niftiImage, dicomMetadata)
        self.nextVol += 1
        return incremental