示例#1
0
    def test_get_tif(self):
        '''tif file returns correct reader'''
        extension='.tif'
        expected = type(vtk.vtkTIFFReader())
        writer = vtk.vtkTIFFWriter()

        filename = os.path.join(self.test_dir, 'file'+extension)
        self.generate_image(filename, writer)
        self.assertEqual(type(get_vtk_reader(filename)), expected)
示例#2
0
    def test_get_nii_gz(self):
        '''compressed nifti file returns correct reader'''
        extension='.nii.gz'
        expected = type(vtk.vtkNIFTIImageReader())
        writer = vtk.vtkNIFTIImageWriter()

        filename = os.path.join(self.test_dir, 'file'+extension)
        self.generate_image(filename, writer)
        self.assertEqual(type(get_vtk_reader(filename)), expected)
示例#3
0
    def test_get_aim(self):
        '''AIM file returns correct reader'''
        extension='.aim'
        expected = type(vtkbone.vtkboneAIMReader())
        writer = vtkbone.vtkboneAIMWriter()

        filename = os.path.join(self.test_dir, 'file'+extension)
        self.generate_image(filename, writer)
        self.assertEqual(type(get_vtk_reader(filename)), expected)
示例#4
0
def ImageConverter(input_filename,
                   output_filename,
                   processing_log='',
                   overwrite=False):
    # Python 2/3 compatible input
    from six.moves import input

    # Check if output exists and should overwrite
    if os.path.isfile(output_filename) and not overwrite:
        result = input('File \"{}\" already exists. Overwrite? [y/n]: '.format(
            output_filename))
        if result.lower() not in ['y', 'yes']:
            print('Not overwriting. Exiting...')
            os.sys.exit()

    # Read input
    if not os.path.isfile(input_filename):
        os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename))

    reader = get_vtk_reader(input_filename)
    if reader is None:
        os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(
            input_filename))

    print('Reading input image ' + input_filename)
    reader.SetFileName(input_filename)
    reader.Update()

    # Create writer
    writer = get_vtk_writer(output_filename)
    if writer is None:
        os.sys.exit('[ERROR] Cannot find writer for file \"{}\"'.format(
            output_filename))
    writer.SetInputConnection(reader.GetOutputPort())
    writer.SetFileName(output_filename)

    # Setup processing log
    final_processing_log = ''
    if len(processing_log) > 0 and type(reader) == type(
            vtkbone.vtkboneAIMReader):
        final_processing_log = reader.GetProcessingLog(
        ) + os.linesep + processing_log
    elif type(reader) == type(vtkbone.vtkboneAIMReader):
        final_processing_log = reader.GetProcessingLog()
    elif len(processing_log) > 0:
        final_processing_log = processing_log

    # Handle edge cases for each output file type
    handle_filetype_writing_special_cases(writer,
                                          processing_log=final_processing_log)

    print('Saving image ' + output_filename)
    writer.Update()
    def test_image_data(self):
        '''Correctly works on image data'''
        filename = os.path.join(self.test_dir, 'test25a.aim')
        r = get_vtk_reader(filename)
        r.SetFileName(filename)
        r.Update()

        array = vtkImageData_to_numpy(r.GetOutput())

        npt.assert_array_almost_equal(array.shape, [25, 25, 25])

        test_points = [[(0, 0, 0), 127], [(0, 0, 24), 0], [(20, 21, 23), 0]]

        for index, value in test_points:
            self.assertAlmostEqual(array[index], value)
示例#6
0
def VisualizeSegmentation(input_filename, segmentation_filename, window, level,
                          nThreads, opacity):
    # Python 2/3 compatible input
    from six.moves import input

    # Read input
    for filename in [input_filename, segmentation_filename]:
        if not os.path.isfile(filename):
            os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(filename))

    # Set a minimum thread count
    nThreads = max(1, nThreads)

    # Max/min opacity
    opacity = max(0, opacity)
    opacity = min(1, opacity)

    # Read the input
    image_reader = get_vtk_reader(input_filename)
    if image_reader is None:
        os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(
            input_filename))

    print('Reading input image ' + input_filename)
    image_reader.SetFileName(input_filename)
    image_reader.Update()

    # Read the segmentation
    seg_reader = get_vtk_reader(segmentation_filename)
    if seg_reader is None:
        os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(
            segmentation_filename))

    print('Reading input image ' + segmentation_filename)
    seg_reader.SetFileName(segmentation_filename)
    seg_reader.Update()

    # Get scalar range for W/L and padding
    image_scalar_range = image_reader.GetOutput().GetScalarRange()

    # Determine if we need to autocompute the window/level
    if window <= 0:
        window = image_scalar_range[1] - image_scalar_range[0]
        level = (image_scalar_range[1] + image_scalar_range[0]) / 2

    # Get data range
    seg_scalar_range = [
        int(x) for x in seg_reader.GetOutput().GetScalarRange()
    ]
    if seg_scalar_range[0] < 0:
        os.sys.exit(
            "Segmentation image \"{}\" has values less than zero which cannot currently be handled. Exiting..."
            .format(segmentation_filename))
    nLabels = seg_scalar_range[1]
    print("Segmented image has {} labels".format(nLabels))

    # Setup LUT
    segLUT = vtk.vtkLookupTable()
    segLUT.SetRange(0, nLabels)
    segLUT.SetRampToLinear()
    segLUT.SetAlphaRange(1, 1)  # Make it slightly transparent
    segLUT.Build()
    segLUT.SetTableValue(0, 0.0, 0.0, 0.0,
                         0.0)  # Set zero to black, transparent

    # Setup input Mapper + Property -> Slice
    inputMapper = vtk.vtkImageResliceMapper()
    inputMapper.SetInputConnection(image_reader.GetOutputPort())
    inputMapper.SliceAtFocalPointOn()
    inputMapper.SliceFacesCameraOn()
    inputMapper.BorderOn()
    inputMapper.SetNumberOfThreads(nThreads)
    inputMapper.ResampleToScreenPixelsOn()
    inputMapper.StreamingOn()

    imageProperty = vtk.vtkImageProperty()
    imageProperty.SetColorLevel(level)
    imageProperty.SetColorWindow(window)
    imageProperty.SetLayerNumber(1)
    imageProperty.SetInterpolationTypeToNearest()

    inputSlice = vtk.vtkImageSlice()
    inputSlice.SetMapper(inputMapper)
    inputSlice.SetProperty(imageProperty)

    # Setup seg Mapper + Property -> Slice
    segImageProperty = vtk.vtkImageProperty()
    segImageProperty.SetLookupTable(segLUT)
    segImageProperty.UseLookupTableScalarRangeOn()
    segImageProperty.SetInterpolationTypeToLinear()
    segImageProperty.SetOpacity(opacity)
    segImageProperty.SetLayerNumber(2)
    segImageProperty.SetInterpolationTypeToNearest()

    segMapper = vtk.vtkImageResliceMapper()
    segMapper.SetInputConnection(seg_reader.GetOutputPort())
    segMapper.SliceAtFocalPointOn()
    segMapper.SliceFacesCameraOn()
    segMapper.BorderOn()
    segMapper.SetNumberOfThreads(nThreads)
    segMapper.ResampleToScreenPixelsOn()
    segMapper.StreamingOn()

    segSlice = vtk.vtkImageSlice()
    segSlice.SetProperty(segImageProperty)
    segSlice.SetMapper(segMapper)

    # Add everything to a vtkImageStack
    imageStack = vtk.vtkImageStack()
    imageStack.AddImage(inputSlice)
    imageStack.AddImage(segSlice)
    imageStack.SetActiveLayer(1)

    # Create Renderer -> RenderWindow -> RenderWindowInteractor -> InteractorStyle
    renderer = vtk.vtkRenderer()
    renderer.AddViewProp(imageStack)

    renderWindow = vtk.vtkRenderWindow()
    renderWindow.AddRenderer(renderer)

    interactor = vtk.vtkRenderWindowInteractor()
    interactorStyle = vtk.vtkInteractorStyleImage()
    interactorStyle.SetInteractionModeToImageSlicing()
    interactorStyle.KeyPressActivationOn()

    interactor.SetInteractorStyle(interactorStyle)
    interactor.SetRenderWindow(renderWindow)

    # Add some functionality to switch layers for window/level
    def layerSwitcher(obj, event):
        if str(interactor.GetKeyCode()) == 'w':
            # Print the w/l for the image
            print("Image W/L: {w}/{l}".format(w=imageProperty.GetColorWindow(),
                                              l=imageProperty.GetColorLevel()))
        elif str(interactor.GetKeyCode()) == 'n':
            # Set interpolation to nearest neighbour (good for voxel visualization)
            imageProperty.SetInterpolationTypeToNearest()
            interactor.Render()
        elif str(interactor.GetKeyCode()) == 'c':
            # Set interpolation to cubic (makes a better visualization)
            imageProperty.SetInterpolationTypeToCubic()
            interactor.Render()
        elif str(interactor.GetKeyCode()) == 'r':
            window = image_scalar_range[1] - image_scalar_range[0]
            level = (image_scalar_range[1] + image_scalar_range[0]) / 2
            imageProperty.SetColorLevel(level)
            imageProperty.SetColorWindow(window)
            interactor.Render()

    # Add ability to switch between active layers
    interactor.AddObserver('KeyPressEvent', layerSwitcher,
                           -1.0)  # Call layerSwitcher as last observer

    # Initialize and go
    interactor.Initialize()
    interactor.Start()
示例#7
0
 def constructPipeline(self, _filename):
   self.filename = _filename
   if self.filename.lower().endswith(".stl"):
     self.reader = vtk.vtkSTLReader()
     self.pipelineType = "PolyData"
     self.pipelineDataType = "PolyData"
   else:  
     self.reader = get_vtk_reader(self.filename)
     self.pipelineDataType = "ImageData"
     
   if self.reader is None:
       os.sys.exit("[ERROR] Cannot find reader for file \"{}\"".format(self.filename))
   self.reader.SetFileName(self.filename)
   self.reader.Update()
   
   if self.pipelineDataType == "ImageData":
     self.setImageDataInfoLog(self.reader.GetOutput())
   elif self.pipelineDataType == "PolyData":
     self.setPolyDataInfoLog(self.reader.GetOutput())
   else:
     print("ERROR: Unknown data type.")
     exit(0)
   
   # Capture the log file if it exists
   if (self.reader.GetClassName() == "vtkboneAIMReader"):
     self.processing_log = self.reader.GetProcessingLog()
     self.dim = self.reader.GetOutput().GetDimensions()
     self.pos = self.reader.GetPosition()
     self.el_size_mm = self.reader.GetElementSize()
     self.extent = self.reader.GetOutput().GetExtent()
     self.origin = self.reader.GetOutput().GetOrigin()
     self.validForExtrusion = True
   else:
     self.processing_log = "No processing log available."
     
   # If the input file is a .stl then start a different pipeline
   
   # Set up the pipeline
   if self.pipelineType == "MarchingCubes":
     
     # Gaussian smoothing
     self.gauss.SetStandardDeviation(self.gaussStandardDeviation, self.gaussStandardDeviation, self.gaussStandardDeviation)
     self.gauss.SetRadiusFactors(self.gaussRadius, self.gaussRadius, self.gaussRadius)
     self.gauss.SetInputConnection(self.reader.GetOutputPort())
     
     # Padding 
     image_extent = self.reader.GetOutput().GetExtent()
     self.pad.SetConstant(0)
     self.pad.SetOutputWholeExtent(image_extent[0]-1,image_extent[1]+1,
                                   image_extent[2]-1,image_extent[3]+1,
                                   image_extent[4]-1,image_extent[5]+1)
     self.pad.SetInputConnection(self.gauss.GetOutputPort())
     
     # Image Resize
     self.resampler.SetResizeMethodToMagnificationFactors()
     self.resampler.SetMagnificationFactors(self.magnification, self.magnification, self.magnification)
     self.resampler.BorderOn()
     self.resampler.SetInputConnection(self.pad.GetOutputPort())
     
     # Marching Cubes
     self.marchingCubes.SetInputConnection(self.resampler.GetOutputPort())
     self.marchingCubes.ComputeGradientsOn()
     self.marchingCubes.ComputeNormalsOn()
     self.marchingCubes.ComputeScalarsOff()
     self.marchingCubes.SetNumberOfContours(1)
     self.marchingCubes.SetValue(0, self.isosurface)
     
     # Transform
     self.transformPolyData.SetInputConnection(self.marchingCubes.GetOutputPort())
     self.transformPolyData.SetTransform(self.rigidBodyTransform)
     
     # Set mapper for image data
     self.mapper.SetInputConnection(self.transformPolyData.GetOutputPort())
     
     # Actor
     self.actor.SetMapper(self.mapper)
     self.actor.GetProperty().SetOpacity(self.actorOpacity)
     self.actor.GetProperty().SetColor(self.actorColor)
     self.actor.SetVisibility(self.actorVisibility)
     self.actor.SetPickable(self.actorPickable)
     
   elif self.pipelineType == "PolyData":
     
     # Transform
     self.transformPolyData.SetInputConnection(self.reader.GetOutputPort())
     self.transformPolyData.SetTransform(self.rigidBodyTransform)
     
     # Set mapper for dataset
     self.mapperDS = vtk.vtkDataSetMapper()
     self.mapperDS.SetInputConnection(self.transformPolyData.GetOutputPort())
     
     # Actor
     self.actor.SetMapper(self.mapperDS)
     self.actor.GetProperty().SetOpacity(self.actorOpacity)
     self.actor.GetProperty().SetColor(self.actorColor)
     self.actor.SetVisibility(self.actorVisibility)
     self.actor.SetPickable(self.actorPickable)
     
   else:
     print("ERROR: No appropriate pipeline!")
     exit(0)
示例#8
0
文件: aix.py 项目: Bonelab/Bonelab
def aix(infile, log, stat, histo, verbose, meta):
    # Python 2/3 compatible input
    from six.moves import input

    debug = False

    # Read input
    if not os.path.isfile(infile):
        os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename))

    reader = get_vtk_reader(infile)
    if reader is None:
        os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(
            input_filename))

    #print('Reading input image ' + infile)
    reader.SetFileName(infile)
    reader.Update()
    image = reader.GetOutput()

    # Precompute some values
    guard = '!-------------------------------------------------------------------------------'
    phys_dim = [
        x * y for x, y in zip(image.GetDimensions(), image.GetSpacing())
    ]
    position = [
        math.floor(x / y)
        for x, y in zip(image.GetOrigin(), image.GetSpacing())
    ]
    size = os.path.getsize(infile)
    names = ['Bytes', 'KBytes', 'MBytes', 'GBytes']
    n_image_voxels = image.GetDimensions()[0] * image.GetDimensions(
    )[1] * image.GetDimensions()[2]
    voxel_volume = image.GetSpacing()[0] * image.GetSpacing(
    )[1] * image.GetSpacing()[2]
    i = 0
    while int(size) > 1024 and i < len(names):
        i += 1
        size = size / 2.0**10

    if (not meta):
        # Print header
        print('')
        print(guard)
        print('!>')
        print(
            '!> dim                            {: >6}  {: >6}  {: >6}'.format(
                *image.GetDimensions()))
        print('!> off                                 x       x       x')
        print(
            '!> pos                            {: >6}  {: >6}  {: >6}'.format(
                *position))
        print(
            '!> element size in mm             {:.4f}  {:.4f}  {:.4f}'.format(
                *image.GetSpacing()))
        print(
            '!> phys dim in mm                 {:.4f}  {:.4f}  {:.4f}'.format(
                *phys_dim))
        print('!>')
        print('!> Type of data               {}'.format(
            image.GetScalarTypeAsString()))
        print('!> Total memory size          {:.1f} {: <10}'.format(
            size, names[i]))
        print(guard)

    # Print log
    if log:
        if (reader.GetClassName() == "vtkboneAIMReader"):
            print(reader.GetProcessingLog())
        else:
            print('!- No log available.')

    # Print meta data information
    if meta:
        if (reader.GetClassName() == "vtkboneAIMReader"):
            log = reader.GetProcessingLog()

        if (not log):
            print('!- No meta data available.')
            exit(1)

        meta_list = []

        p_site = re.compile('^Site[ ]+([0-9]+)')
        p_patient = re.compile('^Index Patient[ ]+([0-9]+)')
        p_measurement = re.compile('^Index Measurement[ ]+([0-9]+)')
        p_name = re.compile('^Patient Name[ ]+ ([\w\-\(\' ]+)')
        p_strip_trailing_zeros = re.compile('[ \t]+$')

        for line in iter(log.splitlines()):
            try:
                m = p_site.search(line)
                if (m):
                    name = 'Site'
                    value = m.group(1)
                    if (value == '20'): value = 'RL'
                    if (value == '21'): value = 'RR'
                    if (value == '38'): value = 'TL'
                    if (value == '39'): value = 'TR'
                    meta_list.append(value)
                    if (debug): print('{0:25s}[{1:s}]'.format("Site", value))

                m = p_patient.search(line)
                if (m):
                    name = 'Index Patient'
                    value = m.group(1)
                    meta_list.append(value)
                    if (debug):
                        print('{0:25s}[{1:s}]'.format("Index Patient", value))

                m = p_measurement.search(line)
                if (m):
                    name = 'Index Measurement'
                    value = m.group(1)
                    meta_list.append(value)
                    if (debug):
                        print('{0:25s}[{1:s}]'.format("Index Measurement",
                                                      value))

                m = p_name.search(line)
                if (m):
                    name = 'Patient Name'
                    name_with_trailing_zeros = m.group(1)
                    value = p_strip_trailing_zeros.sub(
                        '', name_with_trailing_zeros)
                    meta_list.append(value)
                    if (debug):
                        print('{0:25s}[{1:s}]'.format("Patient Name", value))

            except AttributeError:
                print("Error: Cannot find meta data.")
                exit(1)

        for item in meta_list:
            print('\"{0:s}\" '.format(item), end='')
        print('\n', end='')
        #print('\"{0}\" \"{1}\" \"{2}\" \"{3}\"'.format(meta_list[0],meta_list[1],meta_list[2],meta_list[3]))

    # Print Stat
    if stat:
        array = vtk_to_numpy(image.GetPointData().GetScalars()).ravel()
        data = {
            '!> Max       =': array.max(),
            '!> Min       =': array.min(),
            '!> Mean      =': array.mean(),
            '!> SD        =': array.std(),
            '!> TV        =': n_image_voxels * voxel_volume
        }

        max_length = 0
        for measure, outcome in data.items():
            max_length = max(max_length, len(measure))
        formatter = '{{:<{}}} {{:>15.4f}} {{}}'.format(max_length)
        for measure, outcome in data.items():
            if measure == '!> TV        =':
                unit = '[mm^3]'
            else:
                unit = '[1]'
            print(formatter.format(measure, outcome, unit))
        print(guard)

    # Print Histogram
    if histo:
        array = vtk_to_numpy(image.GetPointData().GetScalars()).ravel()

        # The range of values and number of bins are hard-coded. If fewer bins
        # or a different range (i.e. 0 to 127 for char) are wanted then simply
        # adjust these settings
        if (image.GetScalarTypeAsString() == "char"):
            nRange = [-128, 127]
            nBins = 128
        elif (image.GetScalarTypeAsString() == "short"):
            nRange = [-32768, 32767]
            nBins = 128
        else:
            print('!- Unknown data type: {}'.format(
                image.GetScalarTypeAsString()))

        # https://numpy.org/doc/stable/reference/generated/numpy.histogram.html
        hist, bin_edges = np.histogram(array, nBins, nRange, None, None, False)
        nValues = sum(hist)

        print(
            '!>  {:4s} ({:.3s}) : Showing {:d} histogram bins over range of {:d} to {:d}.'
            .format('IND', 'QTY', nBins, *nRange))
        for bin in range(nBins):
            index = nRange[0] + int(bin * (nRange[1] - nRange[0]) /
                                    (nBins - 1))
            count = hist[bin] / nValues  # We normalize so total count = 1
            nStars = int(count * 100)
            if (nStars > 60):
                nStars = 60  # just prevents it from wrapping in the terminal
            print('!> {:4d} ({:.3f}): {:s}'.format(index, count, nStars * '*'))
        print(guard)

    # Print verbose
    if verbose:
        half_slice_size = image.GetDimensions()[0] * image.GetDimensions(
        )[1] * 0.5
        array = vtk_to_numpy(image.GetPointData().GetScalars())
        array = array.reshape(image.GetDimensions()).transpose(2, 1, 0)
        i = 1
        it = np.nditer(array, flags=['multi_index'])
        while not it.finished:
            print("{} {}".format(it.multi_index, it[0]))

            # Print a half slice at a time
            if i % half_slice_size == 0:
                result = input('Continue printing results? (y/n)')
                if result not in ['y', 'yes']:
                    print('')
                    print('Aborting...')
                    break
                print('')

            it.iternext()
            i += 1
示例#9
0
 def test_get_nonexistent_file(self):
     '''Nonexistent file returns None'''
     expected = None
     self.assertEqual(get_vtk_reader(''), expected)
示例#10
0
def SliceViewer(input_filename, window, level, nThreads):
    # Python 2/3 compatible input
    from six.moves import input

    # Read input
    if not os.path.isfile(input_filename):
        os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename))

    # Set a minimum thread count
    nThreads = max(1, nThreads)

    # Read the input
    reader = get_vtk_reader(input_filename)
    if reader is None:
        os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(input_filename))

    print('Reading input image ' + input_filename)
    reader.SetFileName(input_filename)
    reader.Update()

    # Get scalar range for W/L and padding
    scalarRanges = reader.GetOutput().GetScalarRange()

    # Determine if we need to autocompute the window/level
    if window <= 0:
        window = scalarRanges[1] - scalarRanges[0]
        level = (scalarRanges[1] + scalarRanges[0])/2

    # Setup input Mapper + Property -> Slice
    inputMapper = vtk.vtkOpenGLImageSliceMapper()
    inputMapper.SetInputConnection(reader.GetOutputPort())
    inputMapper.SliceAtFocalPointOn()
    inputMapper.SliceFacesCameraOn()
    inputMapper.BorderOn()
    inputMapper.SetNumberOfThreads(nThreads)
    inputMapper.StreamingOn()

    imageProperty = vtk.vtkImageProperty()
    imageProperty.SetColorLevel(level)
    imageProperty.SetColorWindow(window)
    imageProperty.SetInterpolationTypeToNearest()

    inputSlice = vtk.vtkImageSlice()
    inputSlice.SetMapper(inputMapper)
    inputSlice.SetProperty(imageProperty)

    # Create Renderer -> RenderWindow -> RenderWindowInteractor -> InteractorStyle
    renderer = vtk.vtkRenderer()
    renderer.AddActor(inputSlice)

    renderWindow = vtk.vtkRenderWindow()
    renderWindow.AddRenderer(renderer)

    interactor = vtk.vtkRenderWindowInteractor()
    interactorStyle = vtk.vtkInteractorStyleImage()
    interactorStyle.SetInteractionModeToImageSlicing()
    interactorStyle.KeyPressActivationOn()

    interactor.SetInteractorStyle(interactorStyle)
    interactor.SetRenderWindow(renderWindow)

    # Add some functionality to switch layers for window/level
    def layerSwitcher(obj,event):
        if str(interactor.GetKeyCode()) == 'w':
            print("Image W/L: {w}/{l}".format(w=imageProperty.GetColorWindow(), l=imageProperty.GetColorLevel()))
        elif str(interactor.GetKeyCode()) == 'n':
            # Set interpolation to nearest neighbour (good for voxel visualization)
            imageProperty.SetInterpolationTypeToNearest()
            interactor.Render()
        elif str(interactor.GetKeyCode()) == 'c':
            # Set interpolation to cubic (makes a better visualization)
            imageProperty.SetInterpolationTypeToCubic()
            interactor.Render()
        elif str(interactor.GetKeyCode()) == 'r':
            window = scalarRanges[1] - scalarRanges[0]
            level = (scalarRanges[1] + scalarRanges[0])/2
            imageProperty.SetColorLevel(level)
            imageProperty.SetColorWindow(window)
            interactor.Render()

    # Add ability to switch between active layers
    interactor.AddObserver('KeyPressEvent', layerSwitcher, -1.0) # Call layerSwitcher as last observer

    # Initialize and go
    interactor.Initialize()
    interactor.Start()