예제 #1
0
    def test_OpenImage(self):
        """Testing ROI open functionality"""

        tempdir = mkdtemp()
        # crop the test image (size=1388x1040) to be bottom right tile of the test image
        # tiled with 6 cols and 5 rows => ROI= 231x208+1155+832

        orig_big = join(
            pychrm_test_dir,
            'lymphoma_eosin_channel_MCL_test_img_sj-05-3362-R2_001_E.tif')
        orig_cropped = join(
            pychrm_test_dir,
            'lymphoma_eosin_channel_MCL_test_img_sj-05-3362-R2_001_E-t6x5_5_4-l.tiff'
        )
        test_cropped_path = join(tempdir, 'TEST_OpenImageROI.tif')

        x = 1155
        y = 832
        w = 231
        h = 208

        from wndcharm import rect
        bb = rect()
        bb.x = x
        bb.y = y
        bb.w = w
        bb.h = h

        # setting mean = 0 is flag to not use mean in ImageMatrix::OpenImage()
        downsample = 0
        #bb = None
        mean = 0.0
        stddev = 0.0
        try:
            cropped_on_load_im = PyImageMatrix()

            if 1 != cropped_on_load_im.OpenImage(orig_big, downsample, bb,
                                                 mean, stddev):
                self.fail('Could not build an ImageMatrix from ' + orig_big)

            cropped_on_load_im.SaveTiff(test_cropped_path)

            orig_pixels = plt.imread(orig_cropped)
            cropped_pixels = plt.imread(test_cropped_path)

            assert_equal(orig_pixels, cropped_pixels)

        finally:
            rmtree(tempdir)
예제 #2
0
    def test_OpenImage(self):
        """Testing ROI open functionality"""

        tempdir = mkdtemp()
        # crop the test image (size=1388x1040) to be bottom right tile of the test image
        # tiled with 6 cols and 5 rows => ROI= 231x208+1155+832

        orig_big = join(
            pychrm_test_dir,
            'lymphoma_eosin_channel_MCL_test_img_sj-05-3362-R2_001_E.tif')
        orig_cropped = join(
            pychrm_test_dir,
            'lymphoma_eosin_channel_MCL_test_img_sj-05-3362-R2_001_E-t6x5_5_4-l.tiff'
        )
        test_cropped_path = join(tempdir, 'TEST_OpenImageROI.tif')

        x = 1155
        y = 832
        w = 231
        h = 208

        from wndcharm import rect
        bb = rect()
        bb.x = x
        bb.y = y
        bb.w = w
        bb.h = h

        # setting mean = 0 is flag to not use mean in ImageMatrix::OpenImage()
        downsample = 0
        #bb = None
        mean = 0.0
        stddev = 0.0
        try:
            cropped_on_load_im = PyImageMatrix()

            if 1 != cropped_on_load_im.OpenImage(orig_big, downsample, bb,
                                                 mean, stddev):
                self.fail('Could not build an ImageMatrix from ' + orig_big)

            cropped_on_load_im.SaveTiff(test_cropped_path)

            orig_pixels = plt.imread(orig_cropped)
            cropped_pixels = plt.imread(test_cropped_path)

            assert_equal(orig_pixels, cropped_pixels)

        finally:
            rmtree(tempdir)
예제 #3
0
    def GenerateFeatures( self, write_to_disk=True, quiet=True ):
        """@brief Loads precalculated features, or calculates new ones, based on which instance
        attributes have been set, and what their values are.

        write_to_disk (bool) - save features to text file which by convention has extension ".sig"
        
        Returns self for convenience."""

        # 0: What features does the user want?
        # 1: are there features already calculated somewhere?
        # 2: if so are they current/complete/expected/correct?
        # 3: if not, what's left to calculate?
        # 4: Calculate the rest
        # 5: Reduce the features down to what the user asked for

        if self.values is not None and len( self.values ) != 0:
            return self

        # Make sure Feature Vector version string is correct, etc:
        #self.Update()

        partial_load = False
        try:
            self.LoadSigFile( quiet=quiet )
            # FIXME: Here's where you'd calculate a small subset of features
            # and see if they match what was loaded from file. The file could be corrupted
            # incomplete, or calculated with different options, e.g., -S1441
            return self
        except IOError:
            # File doesn't exist
            pass
        except WrongFeatureSetVersionError:
            # File has different feature version than desired
            pass
        except IncompleteFeatureSetError:
            # LoadSigFile should create a FeatureComputationPlan
            if not quiet:
                print 'Loaded {0} features from disk for sample "{1}"'.format(
                        len( self.temp_names ), self.name )
            partial_load = True
            pass

        # All hope is lost, calculate features.

        # Use user-assigned feature computation plan, if provided:
        if self.feature_computation_plan != None:
            comp_plan = self.feature_computation_plan

            # I Commented the following out because the computation plan may only reflect
            # the subset of features that haven't been calculated yet:
            # comp_plan.feature_vec_type seems to only contain the minor version
            # i.e., number after the '.'. Assume major version is the latest.
            #self.feature_set_version = '{0}.{1}'.format( 
            #        feature_vector_major_version, comp_plan.feature_vec_type )
        else:
            major, minor = self.feature_set_version.split('.')
            if minor == '0':
                comp_plan = GenerateFeatureComputationPlan( self.feature_names )
            elif minor == '1':
                comp_plan = wndcharm.StdFeatureComputationPlans.getFeatureSet()
            elif minor == '2':
                comp_plan = wndcharm.StdFeatureComputationPlans.getFeatureSetLong()
            elif minor == '3':
                comp_plan = wndcharm.StdFeatureComputationPlans.getFeatureSetColor()
            elif minor == '4':
                comp_plan = wndcharm.StdFeatureComputationPlans.getFeatureSetColorLong()
            else:
                raise ValueError( "Not sure which features you want." )
            self.feature_computation_plan = comp_plan

        # Here are the ImageMatrix API calls:
        # void normalize(double min, double max, long range, double mean, double stddev);
        # int OpenImage(char *image_file_name, int downsample, rect *bounding_rect, double mean, double stddev);
        # void Rotate (const ImageMatrix &matrix_IN, double angle);

        if self.rot is not None:
            raise NotImplementedError( "FIXME: Implement rotations." )

        if self.x is not None and self.y is not None and self.w is not None and self.h is not None:
            bb = wndcharm.rect()
            bb.x = self.x
            bb.y = self.y
            bb.w = self.w
            bb.h = self.h
        else:
            bb = None

        if self.pixel_intensity_mean:
            mean = self.pixel_intensity_mean
            # stddev arg only used in ImageMatrix::OpenImage() if mean is set
            stddev = self.pixel_intensity_stddev
        else:
            # setting mean = 0 is flag to not use mean in ImageMatrix::OpenImage()
            mean = 0
            stddev = 0

        from .PyImageMatrix import PyImageMatrix

        if isinstance( self.source_filepath, str ):
            the_tiff = PyImageMatrix()
            if 1 != the_tiff.OpenImage( self.source_filepath, self.downsample, bb, mean, stddev ):
                raise ValueError( 'Could not build an ImageMatrix from {0}, check the path.'.\
                    format( self.source_filepath ) )
        elif isinstance( self.source_filepath, wndcharm.ImageMatrix ):
            if self.downsample or mean:
                raise NotImplementedError( 'still need to implement modifying open pixel plane with downsample, mean or stddev' )
            if not bb:
                the_tiff = self.source_filepath
            else:
                # API calls for copying desired pixels into empty ImageMatrix instance:
                # the_tiff is garbage collected on return
                the_tiff = PyImageMatrix()
                # bb only used when calling OpenImage

                # ImageMatrix::submatrix() has a funky signature:
                # void ImageMatrix::submatrix (const ImageMatrix &matrix, const unsigned int x1, const unsigned int y1, const unsigned int x2, const unsigned int y2);
                # where x2 and y2 are INCLUSIVE, i.e., must subtract 1 from both
                x1 = self.x
                y1 = self.y
                x2 = x1 + self.w - 1
                y2 = y1 + self.h - 1
                if 1 != the_tiff.submatrix( self.source_filepath, x1, y1, x2, y2 ):
                    raise ValueError( 'Could not crop bounding box ({0},{1}),({2},{3}) from image "{4}"'.\
                    format( x1, y1, x2, y2, self.source_filepath.source ) )
        else:
            raise ValueError("image parameter 'image_path_or_mat' is not a string or a wndcharm.ImageMatrix")

        # pre-allocate space where the features will be stored (C++ std::vector<double>)
        tmp_vec = wndcharm.DoubleVector( comp_plan.n_features )

        # Get an executor for this plan and run it
        plan_exec = wndcharm.FeatureComputationPlanExecutor( comp_plan )
        plan_exec.run( the_tiff, tmp_vec, 0 )

        # get the feature names from the plan
        comp_names = [ comp_plan.getFeatureNameByIndex(i) for i in xrange( comp_plan.n_features ) ]

        # convert std::vector<double> to native python list of floats
        comp_vals = list( tmp_vec )

        # Feature Reduction/Reorder step:
        # Feature computation may give more features than are asked for by user, or out of order.
        if self.feature_names:
            if self.feature_names != comp_names:
                if partial_load:
                    # If we're here, we've already loaded some but not all of the features
                    # we need. Take what we've already loaded and slap it at the end 
                    # of what was calculated.  Doesn't matter if some of the features are
                    # redundant, because the .index() method returns the first item it finds.
                    # FIXME: if there is overlap between what was loaded and what was 
                    # calculated, check to see that they match.
                    comp_names.extend( self.temp_names )
                    comp_vals.extend( self.temp_values )
                    del self.temp_names
                    del self.temp_values
                self.values = np.array( [ comp_vals[ comp_names.index( name ) ] for name in self.feature_names ] )
        else:
            self.feature_names = comp_names
            self.values = comp_vals

        if not quiet:
            if len( comp_vals ) != len( self ):
                print "CALCULATED {0} TOTAL FEATURES, REDUCED TO: {1}".format(
                        len( comp_vals ), self )
            else:
                print "CALCULATED: " + str( self )

        # FIXME: maybe write to disk BEFORE feature reduce? Provide flag to let user decide?
        if write_to_disk:
            self.ToSigFile( quiet=quiet )

        # Feature names need to be modified for their sampling options.
        # Base case is that channel goes in the innermost parentheses, but really it's not
        # just channel, but all sampling options.
        # For now, let the FeatureSpace constructor code handle the modification of feature names
        # for its own self.feature_names
        return self