示例#1
0
文件: position.py 项目: imcf/cecog
class PositionAnalyzer(PositionCore):

    def __init__(self, *args, **kw):
        super(PositionAnalyzer, self).__init__(*args, **kw)

        if not self.has_timelapse:
            self.settings.set('Processing', 'tracking', False)

        self._makedirs()
        self.add_file_handler(join(self._log_dir, "%s.log" %self.position),
                              self._lvl.DEBUG)

    def _makedirs(self):
        assert isinstance(self.position, basestring)
        assert isinstance(self._out_dir, basestring)

        self._analyzed_dir = join(self._out_dir, "analyzed")
        if self.has_timelapse:
            self._position_dir = join(self._analyzed_dir, self.position)
        else:
            self._position_dir = self._analyzed_dir

        odirs = (self._analyzed_dir,
                 join(self._out_dir, "log"),
                 join(self._out_dir, "log", "_finished"),
                 join(self._out_dir, "hdf5"),
                 join(self._out_dir, "plots"),
                 join(self._position_dir, "statistics"),
                 join(self._position_dir, "gallery"),
                 join(self._position_dir, "channel_gallery"),
                 join(self._position_dir, "images"),
                 join(self._position_dir, "images","_labels"))

        for odir in odirs:
            try:
                makedirs(odir)
            except os.error: # no permissions
                self.logger.error("mkdir %s: failed" %odir)
            else:
                self.logger.info("mkdir %s: ok" %odir)
            setattr(self, "_%s_dir" %basename(odir.lower()).strip("_"), odir)

    def setup_classifiers(self):
        sttg = self.settings
        # processing channel, color channel
        for p_channel, c_channel in self.ch_mapping.iteritems():
            self.settings.set_section('Processing')
            if sttg.get2(self._resolve_name(p_channel, 'classification')):
                sttg.set_section('Classification')
                clf = CommonClassPredictor(
                    clf_dir=sttg.get2(self._resolve_name(p_channel,
                                                         'classification_envpath')),
                    name=p_channel,
                    channels=self._channel_regions(p_channel),
                    color_channel=c_channel)
                clf.importFromArff()
                clf.loadClassifier()
                self.classifiers[p_channel] = clf

    def _convert_tracking_duration(self, option_name):
        """Converts a tracking duration according to the unit and the
        mean time-lapse of the current position.
        Returns number of frames.
        """
        value = self.settings.get(SECTION_NAME_TRACKING, option_name)
        unit = self.settings.get(SECTION_NAME_TRACKING,
                                  'tracking_duration_unit')

        # get mean and stddev for the current position
        info = self.meta_data.get_timestamp_info(self.position)
        if unit == TRACKING_DURATION_UNIT_FRAMES or info is None:
            result = value
        elif unit == TRACKING_DURATION_UNIT_MINUTES:
            result = (value * 60.) / info[0]
        elif unit == TRACKING_DURATION_UNIT_SECONDS:
            result = value / info[0]
        else:
            raise ValueError("Wrong unit '%s' specified." %unit)
        return int(round(result))

    @property
    def _es_options(self):
        transitions = eval(self.settings.get2('tracking_labeltransitions'))
        if not isinstance(transitions[0], tuple):
            transitions = (transitions, )
        evopts = {'transitions': transitions,
                  'backward_labels': map(int, self.settings.get2('tracking_backwardlabels').split(',')),
                  'forward_labels': map(int, self.settings.get2('tracking_forwardlabels').split(',')),
                  'backward_check': self._convert_tracking_duration('tracking_backwardCheck'),
                  'forward_check': self._convert_tracking_duration('tracking_forwardCheck'),
                  'backward_range': self._convert_tracking_duration('tracking_backwardrange'),
                  'forward_range': self._convert_tracking_duration('tracking_forwardrange'),
                  'backward_range_min': self.settings.get2('tracking_backwardrange_min'),
                  'forward_range_min': self.settings.get2('tracking_forwardrange_min'),
                  'max_in_degree': self.settings.get2('tracking_maxindegree'),
                  'max_out_degree': self.settings.get2('tracking_maxoutdegree')}
        return evopts

    def define_exp_features(self):
        features = {}
        for name in self.processing_channels:
            region_features = {}
            for region in REGION_INFO.names[name.lower()]:
                # export all features extracted per regions
                if self.settings.get('Output', 'events_export_all_features') or \
                        self.settings.get('Output', 'export_track_data'):
                    # None means all features
                    region_features[region] = None
                # export selected features from settings
                else:
                    region_features[region] = \
                        self.settings.get('General',
                                          '%s_featureextraction_exportfeaturenames'
                                          % name.lower())
                features[name] = region_features
        return features

    def export_object_counts(self):
        fname = join(self._statistics_dir, 'P%s__object_counts.txt' % self.position)

        # at least the total count for primary is always exported
        ch_info = OrderedDict([('Primary', ('primary', [], []))])
        for name, clf in self.classifiers.iteritems():
            names = clf.class_names.values()
            colors = [clf.hexcolors[n] for n in names]
            ch_info[name] = (clf.regions, names, colors)

        self.timeholder.exportObjectCounts(fname, self.position, self.meta_data, ch_info)
        pplot_ymax = \
            self.settings.get('Output', 'export_object_counts_ylim_max')

        # plot only for primary channel so far!
        self.timeholder.exportPopulationPlots(fname, self._plots_dir, self.position,
                                              self.meta_data, ch_info['Primary'], pplot_ymax)


    def export_object_details(self):
        fname = join(self._statistics_dir,
                        'P%s__object_details.txt' % self.position)
        self.timeholder.exportObjectDetails(fname, excel_style=False)
        fname = join(self._statistics_dir,
                        'P%s__object_details_excel.txt' % self.position)
        self.timeholder.exportObjectDetails(fname, excel_style=True)

    def export_image_names(self):
        self.timeholder.exportImageFileNames(self._statistics_dir,
                                             self.position,
                                             self._imagecontainer._importer,
                                             self.ch_mapping)

    def export_full_tracks(self):
        odir = join(self._statistics_dir, 'full')
        exporter = EventExporter(self.meta_data)
        exporter.full_tracks(self.timeholder, self._tes.visitor_data,
                             self.position, odir)

    def export_graphviz(self, channel_name='Primary', region_name='primary'):
        filename = 'tracking_graph___P%s.dot' %self.position
        exporter = TrackExporter()
        exporter.graphviz_dot(join(self._statistics_dir, filename),
                              self._tracker)

        sample_holders = OrderedDict()
        for frame in self.timeholder.keys():
            channel = self.timeholder[frame][channel_name]
            sample_holders[frame] = channel.get_region(region_name)

        filename = join(self._statistics_dir, filename.replace('.dot', '_features.csv'))
        exporter.tracking_data(filename, sample_holders)

    def export_gallery_images(self):
        for ch_name in self.processing_channels:
            cutter_in = join(self._images_dir, ch_name)
            if isdir(cutter_in):
                cutter_out = join(self._gallery_dir, ch_name)
                self.logger.info("running Cutter for '%s'..." %ch_name)
                image_size = \
                    self.settings.get('Output', 'events_gallery_image_size')
                EventGallery(self._tes, cutter_in, self.position, cutter_out,
                             self.meta_data, oneFilePerTrack=True,
                             size=(image_size, image_size))
            # FIXME: be careful here. normally only raw images are
            #        used for the cutter and can be deleted
            #        afterwards
            shutil.rmtree(cutter_in, ignore_errors=True)

    def export_tracks_hdf5(self):
        """Save tracking data to hdf file"""
        self.logger.debug("--- serializing tracking start")
        self.timeholder.serialize_tracking(self._tes.graph)
        self.logger.debug("--- serializing tracking ok")

    def export_events(self):
        """Export and save event selceciton data"""
        exporter = EventExporter(self.meta_data)
        # writes to the event folder
        odir = join(self._statistics_dir, "events")
        exporter.track_features(self.timeholder, self._tes.visitor_data,
                                self.export_features, self.position, odir)
        self.logger.debug("--- visitor analysis ok")
        # writes event data to hdf5
        self.timeholder.serialize_events(self._tes)
        self.logger.debug("--- serializing events ok")

    def __call__(self):
        # include hdf5 file name in hdf5_options
        # perhaps timeholder might be a good placke to read out the options
        # fils must not exist to proceed
        hdf5_fname = join(self._hdf5_dir, '%s.ch5' % self.position)

        self.timeholder = TimeHolder(self.position, self._all_channel_regions,
                                     hdf5_fname,
                                     self.meta_data, self.settings,
                                     self._frames,
                                     self.plate_id,
                                     **self._hdf_options)

        self.settings.set_section('Tracking')
        # setup tracker
        if self.settings.get('Processing', 'tracking'):
            region = self.settings.get('Tracking', 'tracking_regionname')
            tropts = (self.settings.get('Tracking', 'tracking_maxobjectdistance'),
                      self.settings.get('Tracking', 'tracking_maxsplitobjects'),
                      self.settings.get('Tracking', 'tracking_maxtrackinggap'))
            self._tracker = Tracker(*tropts)
            self._tes = EventSelection(self._tracker.graph, **self._es_options)

        stopwatch = StopWatch(start=True)
        ca = CellAnalyzer(timeholder=self.timeholder,
                          position = self.position,
                          create_images = True,
                          binning_factor = 1,
                          detect_objects = self.settings.get('Processing',
                                                             'objectdetection'))

        self.setup_classifiers()
        self.export_features = self.define_exp_features()
        n_images = self._analyze(ca)

        if n_images > 0:
            # invoke event selection
            if self.settings.get('Processing', 'tracking_synchronize_trajectories') and \
                    self.settings.get('Processing', 'tracking'):
                self.logger.debug("--- visitor start")
                self._tes.find_events()
                self.logger.debug("--- visitor ok")
                if self.is_aborted():
                    return 0 # number of processed images

            # save all the data of the position, no aborts from here on
            # want all processed data saved
            if self.settings.get('Output', 'export_object_counts'):
                self.export_object_counts()
            if self.settings.get('Output', 'export_object_details'):
                self.export_object_details()
            if self.settings.get('Output', 'export_file_names'):
                self.export_image_names()

            if self.settings.get('Processing', 'tracking'):
                self.export_tracks_hdf5()
                self.update_status({'text': 'export events...'})
                if self.settings.get('Processing', 'tracking_synchronize_trajectories'):
                    self.export_events()
                if self.settings.get('Output', 'export_track_data'):
                    self.export_full_tracks()
                if self.settings.get('Output', 'export_tracking_as_dot'):
                    self.export_graphviz()

            self.update_status({'text': 'export events...',
                                'max': 1,
                                'progress': 1})

            # remove all features from all channels to free memory
            # for the generation of gallery images
            self.timeholder.purge_features()
            if self.settings.get('Output', 'events_export_gallery_images'):
                self.export_gallery_images()

        try:
            intval = stopwatch.stop()/n_images*1000
        except ZeroDivisionError:
            pass
        else:
            self.logger.info(" - %d image sets analyzed, %3d ms per image set" %
                             (n_images, intval))

        self.touch_finished()
#        self.clear()
        return n_images

    @property
    def hdf5_filename(self):
        return self.timeholder.hdf5_filename

    def touch_finished(self, times=None):
        """Writes an empty file to mark this position as finished"""
        fname = join(self._finished_dir, '%s__finished.txt' % self.position)
        with open(fname, "w") as f:
            os.utime(fname, times)

    def clear(self):
        # closes hdf5
        self.timeholder.close_all()

    def _analyze(self, cellanalyzer):
        super(PositionAnalyzer, self)._analyze()
        n_images = 0
        stopwatch = StopWatch(start=True)
        crd = Coordinate(self.plate_id, self.position,
                         self._frames, list(set(self.ch_mapping.values())))

        for frame, channels in self._imagecontainer( \
            crd, interrupt_channel=True, interrupt_zslice=True):

            if self.is_aborted():
                self.clear()
                return 0
            else:
                txt = 'T %d (%d/%d)' %(frame, self._frames.index(frame)+1,
                                       len(self._frames))
                self.update_status({'progress': self._frames.index(frame)+1,
                                    'text': txt,
                                    'interval': stopwatch.interim()})

            stopwatch.reset(start=True)
            cellanalyzer.initTimepoint(frame)
            self.register_channels(cellanalyzer, channels)

            cellanalyzer.process()
            n_images += 1
            images = []

            if self.settings.get('Processing', 'tracking'):
                region = self.settings.get('Tracking', 'tracking_regionname')
                samples = self.timeholder[frame][PrimaryChannel.NAME].get_region(region)
                self._tracker.track_next_frame(frame, samples)

                if self.settings.get('Tracking', 'tracking_visualization'):
                    size = cellanalyzer.getImageSize(PrimaryChannel.NAME)
                    nframes = self.settings.get('Tracking', 'tracking_visualize_track_length')
                    radius = self.settings.get('Tracking', 'tracking_centroid_radius')
                    img_conn, img_split = self._tracker.render_tracks(
                        frame, size, nframes, radius)
                    images += [(img_conn, '#FFFF00', 1.0),
                               (img_split, '#00FFFF', 1.0)]

            for clf in self.classifiers.itervalues():
                cellanalyzer.classify_objects(clf)

            ##############################################################
            # FIXME - part for browser
            if not self._myhack is None:
                self.render_browser(cellanalyzer)
            ##############################################################

            self.settings.set_section('General')
            self.render_classification_images(cellanalyzer, images, frame)
            self.render_contour_images(cellanalyzer, images, frame)

            if self.settings.get('Output', 'rendering_channel_gallery'):
                self.render_channel_gallery(cellanalyzer, frame)

            if self.settings.get('Output', 'rendering_labels_discwrite'):
                cellanalyzer.exportLabelImages(self._labels_dir)

            self.logger.info(" - Frame %d, duration (ms): %3d" \
                                 %(frame, stopwatch.interim()*1000))
            cellanalyzer.purge(features=self.export_features)

        return n_images

    def render_channel_gallery(self, cellanalyzer, frame):
        for channel in cellanalyzer.virtual_channels.itervalues():
            chgal = ChannelGallery(channel, frame, self._channel_gallery_dir)
            chgal.make_gallery()

    def render_contour_images(self, ca, images, frame):
        for region, render_par in self.settings.get2('rendering').iteritems():
            out_dir = join(self._images_dir, region)
            write = self.settings.get('Output', 'rendering_contours_discwrite')

            if region not in self.CHANNELS.keys():
                img, fname = ca.render(out_dir, dctRenderInfo=render_par,
                                       writeToDisc=write, images=images)
                msg = 'PL %s - P %s - T %05d' %(self.plate_id, self.position,
                                                frame)
                self.set_image(img, msg, fname, region, 50)
            # gallery images are treated differenty
            else:
                ca.render(out_dir, dctRenderInfo=render_par, writeToDisc=True)

    def render_classification_images(self, cellanalyzer, images, frame):
        for region, render_par in self.settings.get2('rendering_class').iteritems():
            out_images = join(self._images_dir, region)
            write = self.settings.get('Output', 'rendering_class_discwrite')
            img_rgb, fname = cellanalyzer.render(out_images,
                                                 dctRenderInfo=render_par,
                                                 writeToDisc=write,
                                                 images=images)

            msg = 'PL %s - P %s - T %05d' %(self.plate_id, self.position, frame)
            self.set_image(img_rgb, msg, fname, region, 50)

    def render_browser(self, cellanalyzer):
        d = {}
        for name in cellanalyzer.get_channel_names():
            channel = cellanalyzer.get_channel(name)
            d[channel.strChannelId] = channel.meta_image.image
            self._myhack.show_image(d)

        channel_name, region_name = self._myhack._object_region
        channel = cellanalyzer.get_channel(channel_name)
        if channel.has_region(region_name):
            region = channel.get_region(region_name)
            coords = {}
            for obj_id, obj in region.iteritems():
                coords[obj_id] = obj.crack_contour
            self._myhack.set_coords(coords)
示例#2
0
class PositionAnalyzer(PositionCore):

    def __init__(self, *args, **kw):
        super(PositionAnalyzer, self).__init__(*args, **kw)

        if not self.has_timelapse:
            self.settings.set('Processing', 'tracking', False)

        self._makedirs()
        self.add_file_handler(join(self._log_dir, "%s.log" %self.position),
                              self.Levels.DEBUG)

    def _makedirs(self):
        assert isinstance(self.position, basestring)
        assert isinstance(self._out_dir, basestring)

        self._analyzed_dir = join(self._out_dir, "analyzed")
        if self.has_timelapse:
            self._position_dir = join(self._analyzed_dir, self.position)
        else:
            self._position_dir = self._analyzed_dir

        odirs = (self._analyzed_dir,
                 join(self._out_dir, "log"),
                 join(self._out_dir, "log", "_finished"),
                 join(self._out_dir, "hdf5"),
                 join(self._out_dir, "plots"),
                 join(self._position_dir, "statistics"),
                 join(self._position_dir, "tc3"),
                 join(self._position_dir, "gallery"),
                 join(self._position_dir, "channel_gallery"),
                 join(self._position_dir, "images"),
                 join(self._position_dir, "images","_labels"))

        for odir in odirs:
            try:
                makedirs(odir)
            except os.error: # no permissions
                self.logger.error("mkdir %s: failed" %odir)
            else:
                self.logger.info("mkdir %s: ok" %odir)
            setattr(self, "_%s_dir" %basename(odir.lower()).strip("_"), odir)

    def setup_classifiers(self):
        sttg = self.settings

        # processing channel, color channel
        for p_channel, c_channel in self.ch_mapping.iteritems():
            self.settings.set_section('Processing')
            if sttg.get2(self._resolve_name(p_channel, 'classification')):
                chreg = self._channel_regions(p_channel)
                if sttg("EventSelection", "unsupervised_event_selection"):
                    nclusters = sttg("EventSelection", "num_clusters")
                    self.classifiers[p_channel] = ClassDefinitionUnsup( \
                        nclusters, chreg)
                else:
                    sttg.set_section('Classification')
                    clf = CommonClassPredictor(
                        clf_dir=sttg.get2(self._resolve_name(p_channel,
                                                             'classification_envpath')),
                        name=p_channel,
                        channels=chreg,
                        color_channel=c_channel)
                    clf.importFromArff()
                    clf.loadClassifier()
                    self.classifiers[p_channel] = clf

    @property
    def _transitions(self):
        if self.settings.get('EventSelection', 'unsupervised_event_selection'):
            transitions = np.array(((0, 1), ))
        else:
            try:
                transitions = np.array(eval(self.settings.get('EventSelection', 'labeltransitions')))
                transitions.reshape((-1, 2))
            except Exception as e:
                raise RuntimeError(("Make sure that transitions are of the form "
                                    "'int, int' or '(int, int), (int, int)' i.e "
                                    "2-int-tuple  or a list of 2-int-tuples"))

        return transitions

    def setup_eventselection(self, graph):
        """Setup the method for event selection."""

        opts = {'transitions': self._transitions,
                'backward_range': self._convert_tracking_duration('backwardrange'),
                'forward_range': self._convert_tracking_duration('forwardrange'),
                'max_in_degree': self.settings.get('EventSelection', 'maxindegree'),
                'max_out_degree': self.settings.get('EventSelection', 'maxoutdegree')}

        if self.settings.get('EventSelection', 'supervised_event_selection'):
            opts.update({'backward_labels': [int(i) for i in self.settings.get('EventSelection', 'backwardlabels').split(',')],
                         'forward_labels': [int(i) for i in self.settings.get('EventSelection', 'forwardlabels').split(',')],
                         'backward_range_min': self.settings.get('EventSelection', 'backwardrange_min'),
                         'forward_range_min': self.settings.get('EventSelection', 'forwardrange_min'),
                         'backward_check': self._convert_tracking_duration('backwardCheck'),
                         'forward_check': self._convert_tracking_duration('forwardCheck')})
            es = EventSelection(graph, **opts)

        elif self.settings.get('EventSelection', 'unsupervised_event_selection'):
            cld = self.classifiers.values()[0] # only one classdef in case of UES
            opts.update({'forward_check': self._convert_tracking_duration('min_event_duration'),
                         'forward_labels': (1, ),
                         'backward_check': -1, # unsused for unsupervised usecase
                         'backward_labels': (0, ),
                         'num_clusters': self.settings.get('EventSelection', 'num_clusters'),
                         'min_cluster_size': self.settings.get('EventSelection', 'min_cluster_size'),
                         'classdef': cld})
            es = UnsupervisedEventSelection(graph, **opts)

        return es

    def _convert_tracking_duration(self, option_name):
        """Converts a tracking duration according to the unit and the
        mean time-lapse of the current position.
        Returns number of frames.
        """
        value = self.settings.get('EventSelection', option_name)
        unit = self.settings.get('EventSelection', 'duration_unit')

        # get mean and stddev for the current position
        info = self.meta_data.get_timestamp_info(self.position)
        if unit == TimeConverter.FRAMES or info is None:
            result = value
        elif unit == TimeConverter.MINUTES:
            result = (value * 60.) / info[0]
        elif unit == TimeConverter.SECONDS:
            result = value / info[0]
        else:
            raise ValueError("Wrong unit '%s' specified." %unit)
        return int(round(result))

    def define_exp_features(self):
        features = {}
        for name in self.processing_channels:
            region_features = {}

            for region in MetaPluginManager().region_info.names[name.lower()]:
                if name is self.MERGED_CHANNEL:
                    continue
                # export all features extracted per regions
                if self.settings.get('Output', 'events_export_all_features') or \
                        self.settings.get('Output', 'export_track_data'):
                    # None means all features
                    region_features[region] = None
                # export selected features from settings
                else:
                    region_features[region] = \
                        self.settings.get('General',
                                          '%s_featureextraction_exportfeaturenames'
                                          % name.lower())

                features[name] = region_features

            # special case for merged channel
            if name is self.MERGED_CHANNEL:
                mftrs = list()
                for channel, region in self._channel_regions(name).iteritems():
                    if features[channel][region] is None:
                        mftrs = None
                    else:
                        for feature in features[channel][region]:
                            mftrs.append("_".join((channel, region, feature)))
                region_features[self._all_channel_regions[name].values()] = mftrs
                features[name] = region_features

        return features

    def export_object_counts(self):
        fname = join(self._statistics_dir, 'P%s__object_counts.txt' % self.position)

        ch_info = OrderedDict()
        for name, clf in self.classifiers.iteritems():
            names = clf.class_names.values()
            colors = [clf.hexcolors[n] for n in names]
            ch_info[name] = (clf.regions, names, colors)

        # if no classifier has been loaded, no counts can be exported.
        if len(ch_info) == 0:
            return

        self.timeholder.exportObjectCounts(fname, self.position, self.meta_data, ch_info)
        pplot_ymax = \
            self.settings.get('Output', 'export_object_counts_ylim_max')

        self.timeholder.exportPopulationPlots(ch_info, self._plots_dir,
                                              self.plate_id, self.position,
                                              ymax=pplot_ymax)



    def export_object_details(self):
        fname = join(self._statistics_dir,
                        'P%s__object_details.txt' % self.position)
        self.timeholder.exportObjectDetails(fname)

    def export_image_names(self):
        self.timeholder.exportImageFileNames(self._statistics_dir,
                                             self.position,
                                             self._imagecontainer._importer,
                                             self.ch_mapping)

    def export_full_tracks(self):
        odir = join(self._statistics_dir, 'full')
        exporter = EventExporter(self.meta_data)
        exporter.full_tracks(self.timeholder, self._tes.visitor_data,
                             self.position, odir)

    def export_graphviz(self, channel_name='Primary', region_name='primary'):
        filename = 'tracking_graph___P%s.dot' %self.position
        exporter = TrackExporter()
        exporter.graphviz_dot(join(self._statistics_dir, filename),
                              self._tracker)

        sample_holders = OrderedDict()
        for frame in self.timeholder.keys():
            channel = self.timeholder[frame][channel_name]
            sample_holders[frame] = channel.get_region(region_name)

        filename = join(self._statistics_dir, filename.replace('.dot', '_features.csv'))
        exporter.tracking_data(filename, sample_holders)

    def export_gallery_images(self):
        for ch_name in self.processing_channels:
            cutter_in = join(self._images_dir, ch_name.lower())

            if not isdir(cutter_in):
                self.logger.warning('directory not found (%s)' %cutter_in)
                self.logger.warning('can not write the gallery images')
            else:
                cutter_out = join(self._gallery_dir, ch_name.lower())
                self.logger.info("running Cutter for '%s'..." %ch_name)
                image_size = \
                    self.settings.get('Output', 'events_gallery_image_size')
                TrackGallery(self._tes.centers(),
                             cutter_in, cutter_out, self.position, size=image_size)
            # FIXME: be careful here. normally only raw images are
            #        used for the cutter and can be deleted
            #        afterwards
            shutil.rmtree(cutter_in, ignore_errors=True)

    def export_tracks_hdf5(self):
        """Save tracking data to hdf file"""
        self.logger.debug("--- serializing tracking start")
        self.timeholder.serialize_tracking(self._tracker.graph)
        self.logger.debug("--- serializing tracking ok")

    def export_events(self):
        """Export and save event selection data"""
        exporter = EventExporter(self.meta_data)
        # writes to the event folder
        odir = join(self._statistics_dir, "events")
        exporter.track_features(self.timeholder, self._tes.visitor_data,
                                self.export_features, self.position, odir)
        self.logger.debug("--- serializing events ok")

    def export_events_hdf5(self):
        # writes event data to hdf5
        self.timeholder.serialize_events(self._tes)

    def export_tc3(self):
        t_mean = self.meta_data.get_timestamp_info(self.position)[0]
        tu = TimeConverter(t_mean, TimeConverter.SECONDS)
        increment = self.settings('General', 'frameincrement')
        t_step = tu.sec2min(t_mean)*increment

        nclusters = self.settings.get('EventSelection', 'num_clusters')
        exporter = TC3Exporter(self._tes.tc3data, self._tc3_dir, nclusters,
                               t_step, TimeConverter.MINUTES, self.position)
        exporter()

    def export_classlabels(self):
        """Save classlabels of each object to the hdf file."""
        # function works for supervised and unuspervised case
        for channels in self.timeholder.itervalues():
            for chname, classifier in self.classifiers.iteritems():
                holder = channels[chname].get_region(classifier.regions)
                if classifier.feature_names is None:
                    # special for unsupervised case
                    classifier.feature_names = holder.feature_names
                self.timeholder.save_classlabels(channels[chname],
                                                 holder, classifier)

    def __call__(self):
        # include hdf5 file name in hdf5_options
        # perhaps timeholder might be a good place to read out the options
        # file does not have to exist to proceed
        hdf5_fname = join(self._hdf5_dir, '%s.ch5' % self.position)

        self.timeholder = TimeHolder(self.position, self._all_channel_regions,
                                     hdf5_fname,
                                     self.meta_data, self.settings,
                                     self._frames,
                                     self.plate_id,
                                     **self._hdf_options)

        self.settings.set_section('Tracking')
        self.setup_classifiers()

        # setup tracker
        if self.settings('Processing', 'tracking'):
            tropts = (self.settings('Tracking', 'tracking_maxobjectdistance'),
                      self.settings('Tracking', 'tracking_maxsplitobjects'),
                      self.settings('Tracking', 'tracking_maxtrackinggap'))
            self._tracker = Tracker(*tropts)

        stopwatch = StopWatch(start=True)
        ca = CellAnalyzer(timeholder=self.timeholder,
                          position = self.position,
                          create_images = True,
                          binning_factor = 1,
                          detect_objects = self.settings('Processing',
                                                         'objectdetection'))

        self.export_features = self.define_exp_features()
        n_images = self._analyze(ca)

        if n_images > 0:
            # invoke event selection
            if self.settings('Processing', 'eventselection') and \
                    self.settings('Processing', 'tracking'):

                evchannel = self.settings('EventSelection', 'eventchannel')
                region = self.classifiers[evchannel].regions
                if self.settings('EventSelection', 'unsupervised_event_selection'):
                    graph = self._tracker.graph
                elif  evchannel != PrimaryChannel.NAME or \
                        region != self.settings("Tracking", "region"):
                    graph = self._tracker.clone_graph(self.timeholder,
                                                      evchannel,
                                                      region)
                else:
                    graph = self._tracker.graph

                self._tes = self.setup_eventselection(graph)
                self.logger.debug("--- visitor start")
                self._tes.find_events()
                self.logger.debug("--- visitor ok")
                if self.is_aborted():
                    return 0 # number of processed images

            # save all the data of the position, no aborts from here on
            # want all processed data saved
            if self.settings('Output', 'export_object_counts') and \
                    self.settings('EventSelection', 'supervised_event_selection'):
                # no object counts in case of unsupervised event selection
                self.export_object_counts()
            if self.settings('Output', 'export_object_details'):
                self.export_object_details()
            if self.settings('Output', 'export_file_names'):
                self.export_image_names()

            if self.settings('Processing', 'tracking'):
                self.export_tracks_hdf5()
                self.update_status({'text': 'export events...'})

                if self.settings('Output', 'hdf5_include_events'):
                    self.export_events_hdf5()

                if self.settings('Output', "export_events"):
                    if self.settings('Processing', 'eventselection'):
                        self.export_events()
                    if self.settings('EventSelection', 'unsupervised_event_selection'):
                        self.export_tc3()

                if self.settings('Output', 'export_track_data'):
                    self.export_full_tracks()
                if self.settings('Output', 'export_tracking_as_dot'):
                    self.export_graphviz(channel_name =PrimaryChannel.NAME,\
                                          region_name =self._all_channel_regions[PrimaryChannel.NAME][PrimaryChannel.NAME])

            self.export_classlabels()

            self.update_status({'text': 'export events...',
                                'max': 1,
                                'progress': 1})

            # remove all features from all channels to free memory
            # for the generation of gallery images
            self.timeholder.purge_features()
            if self.settings.get('Output', 'events_export_gallery_images') and \
                    self.settings.get('Processing', 'eventselection'):
                self.export_gallery_images()

        try:
            intval = stopwatch.stop()/n_images*1000
        except ZeroDivisionError:
            pass
        else:
            self.logger.info(" - %d image sets analyzed, %3d ms per image set" %
                             (n_images, intval))

        self.touch_finished()
        self.clear()
        return n_images

    @property
    def hdf5_filename(self):
        return self.timeholder.hdf5_filename

    def touch_finished(self, times=None):
        """Writes an empty file to mark this position as finished"""
        fname = join(self._finished_dir, '%s__finished.txt' % self.position)
        with open(fname, "w") as f:
            os.utime(fname, times)

    def clear(self):
        # closes
        if self.timeholder is not None:
            self.timeholder.close_all()
        # close and remove handlers from logging object
        self.close()

    def _analyze(self, cellanalyzer):
        super(PositionAnalyzer, self)._analyze()
        n_images = 0
        stopwatch = StopWatch(start=True)
        crd = Coordinate(self.plate_id, self.position,
                         self._frames, list(set(self.ch_mapping.values())))

        minimal_effort = self.settings.get('Output', 'minimal_effort') and self.settings.get('Output', 'hdf5_reuse')

        for frame, channels in self._imagecontainer( \
            crd, interrupt_channel=True, interrupt_zslice=True):

            if self.is_aborted():
                self.clear()
                return 0
            else:
                txt = 'T %d (%d/%d)' %(frame, self._frames.index(frame)+1,
                                       len(self._frames))
                self.update_status({'progress': self._frames.index(frame)+1,
                                    'text': txt,
                                    'interval': stopwatch.interim()})

            stopwatch.reset(start=True)
            cellanalyzer.initTimepoint(frame)
            self.register_channels(cellanalyzer, channels)

            cellanalyzer.process()

            self.logger.info(" - Frame %d, cellanalyzer.process (ms): %3d" \
                             %(frame, stopwatch.interval()*1000))

            n_images += 1
            images = []

            if self.settings('Processing', 'tracking'):
                region = self.settings('Tracking', 'region')
                samples = self.timeholder[frame][PrimaryChannel.NAME].get_region(region)
                self._tracker.track_next_frame(frame, samples)

                if self.settings('Tracking', 'tracking_visualization'):
                    size = cellanalyzer.getImageSize(PrimaryChannel.NAME)
                    nframes = self.settings('Tracking', 'tracking_visualize_track_length')
                    radius = self.settings('Tracking', 'tracking_centroid_radius')
                    img_conn, img_split = self._tracker.render_tracks(
                        frame, size, nframes, radius)
                    images += [(img_conn, '#FFFF00', 1.0),
                               (img_split, '#00FFFF', 1.0)]

            self.logger.info(" - Frame %d, Tracking (ms): %3d" \
                             %(frame, stopwatch.interval()*1000))

            # can't cluster on a per frame basis
            if self.settings("EventSelection", "supervised_event_selection"):
                for clf in self.classifiers.itervalues():
                    cellanalyzer.classify_objects(clf)

            self.logger.info(" - Frame %d, Classification (ms): %3d" \
                             % (frame, stopwatch.interval()*1000))

            self.settings.set_section('General')
            # want emit all images at once
            if not minimal_effort:
                imgs = {}
                imgs.update(self.render_classification_images(cellanalyzer, images, frame))
                imgs.update(self.render_contour_images(cellanalyzer, images, frame))
                msg = 'PL %s - P %s - T %05d' %(self.plate_id, self.position, frame)
                self.set_image(imgs, msg, 50)

                if self.settings('Output', 'rendering_channel_gallery'):
                    self.render_channel_gallery(cellanalyzer, frame)

                if self.settings('Output', 'rendering_labels_discwrite'):
                    cellanalyzer.exportLabelImages(self._labels_dir)

            cellanalyzer.purge(features=self.export_features)
            self.logger.info(" - Frame %d, rest (ms): %3d" \
                                 %(frame, stopwatch.interval()*1000))
            self.logger.info(" - Frame %d, duration (ms): %3d" \
                                 %(frame, stopwatch.interim()*1000))


        return n_images

    def render_channel_gallery(self, cellanalyzer, frame):
        for channel in cellanalyzer.virtual_channels.itervalues():
            chgal = ChannelGallery(channel, frame, self._channel_gallery_dir)
            chgal.make_gallery()

    def render_contour_images(self, ca, images, frame):
        images_ = dict()
        for region, render_par in self.settings.get2('rendering').iteritems():
            out_dir = join(self._images_dir, region)
            write = self.settings('Output', 'rendering_contours_discwrite')

            if region not in self.CHANNELS.keys():
                img, _ = ca.render(out_dir, dctRenderInfo=render_par,
                                       writeToDisc=write, images=images)


                images_[region] = img
            # gallery images are treated differenty
            else:
                ca.render(out_dir, dctRenderInfo=render_par, writeToDisc=True)
        return images_

    def render_classification_images(self, cellanalyzer, images, frame):
         images_ = dict()
         for region, render_par in self.settings.get2('rendering_class').iteritems():
            out_images = join(self._images_dir, region)
            write = self.settings('Output', 'rendering_class_discwrite')
            image, _ = cellanalyzer.render(out_images,
                                                 dctRenderInfo=render_par,
                                                 writeToDisc=write,
                                                 images=images)
            images_[region] = image
         return images_
示例#3
0
class PositionAnalyzer(PositionCore):

    def __init__(self, *args, **kw):
        super(PositionAnalyzer, self).__init__(*args, **kw)

        if not self.has_timelapse:
            self.settings.set('Processing', 'tracking', False)

        self._makedirs()
        self.add_file_handler(join(self._log_dir, "%s.log" %self.position),
                              self.Levels.DEBUG)

    def _makedirs(self):
        assert isinstance(self.position, basestring)
        assert isinstance(self._out_dir, basestring)

        self._analyzed_dir = join(self._out_dir, "analyzed")
        if self.has_timelapse:
            self._position_dir = join(self._analyzed_dir, self.position)
        else:
            self._position_dir = self._analyzed_dir

        odirs = (self._analyzed_dir,
                 join(self._out_dir, "log"),
                 join(self._out_dir, "log", "_finished"),
                 join(self._out_dir, "hdf5"),
                 join(self._out_dir, "plots"),
                 join(self._position_dir, "statistics"),
                 join(self._position_dir, "tc3"),
                 join(self._position_dir, "gallery"),
                 join(self._position_dir, "channel_gallery"),
                 join(self._position_dir, "images"),
                 join(self._position_dir, "images","_labels"))

        for odir in odirs:
            try:
                makedirs(odir)
            except os.error: # no permissions
                self.logger.error("mkdir %s: failed" %odir)
            else:
                self.logger.info("mkdir %s: ok" %odir)
            setattr(self, "_%s_dir" %basename(odir.lower()).strip("_"), odir)

    def setup_classifiers(self):
        sttg = self.settings

        # processing channel, color channel
        for p_channel, c_channel in self.ch_mapping.iteritems():
            self.settings.set_section('Processing')
            if sttg.get2(self._resolve_name(p_channel, 'classification')):
                chreg = self._channel_regions(p_channel)
                if sttg("EventSelection", "unsupervised_event_selection"):
                    nclusters = sttg("EventSelection", "num_clusters")
                    self.classifiers[p_channel] = ClassDefinitionUnsup( \
                        nclusters, chreg)
                else:
                    sttg.set_section('Classification')
                    clf = CommonClassPredictor(
                        clf_dir=sttg.get2(self._resolve_name(p_channel,
                                                             'classification_envpath')),
                        name=p_channel,
                        channels=chreg,
                        color_channel=c_channel)
                    clf.importFromArff()
                    clf.loadClassifier()
                    self.classifiers[p_channel] = clf

    @property
    def _transitions(self):
        if self.settings.get('EventSelection', 'unsupervised_event_selection'):
            transitions = np.array((0, 1))
        else:
            try:
                transitions = np.array(eval(self.settings.get('EventSelection', 'labeltransitions')))
                transitions.reshape((-1, 2))
            except Exception as e:
                raise RuntimeError(("Make sure that transitions are of the form "
                                    "'int, int' or '(int, int), (int, int)' i.e "
                                    "2-int-tuple  or a list of 2-int-tuples"))

        return transitions

    def setup_eventselection(self, graph):
        """Setup the method for event selection."""

        opts = {'transitions': self._transitions,
                'backward_range': self._convert_tracking_duration('backwardrange'),
                'forward_range': self._convert_tracking_duration('forwardrange'),
                'max_in_degree': self.settings.get('EventSelection', 'maxindegree'),
                'max_out_degree': self.settings.get('EventSelection', 'maxoutdegree')}

        if self.settings.get('EventSelection', 'supervised_event_selection'):
            opts.update({'backward_labels': [int(i) for i in self.settings.get('EventSelection', 'backwardlabels').split(',')],
                         'forward_labels': [int(i) for i in self.settings.get('EventSelection', 'forwardlabels').split(',')],
                         'backward_range_min': self.settings.get('EventSelection', 'backwardrange_min'),
                         'forward_range_min': self.settings.get('EventSelection', 'forwardrange_min'),
                         'backward_check': self._convert_tracking_duration('backwardCheck'),
                         'forward_check': self._convert_tracking_duration('forwardCheck')})
            es = EventSelection(graph, **opts)

        elif self.settings.get('EventSelection', 'unsupervised_event_selection'):
            cld = self.classifiers.values()[0] # only one classdef in case of UES
            opts.update({'forward_check': self._convert_tracking_duration('min_event_duration'),
                         'forward_labels': (1, ),
                         'backward_check': -1, # unsused for unsupervised usecase
                         'backward_labels': (0, ),
                         'num_clusters': self.settings.get('EventSelection', 'num_clusters'),
                         'min_cluster_size': self.settings.get('EventSelection', 'min_cluster_size'),
                         'classdef': cld})
            es = UnsupervisedEventSelection(graph, **opts)

        return es

    def _convert_tracking_duration(self, option_name):
        """Converts a tracking duration according to the unit and the
        mean time-lapse of the current position.
        Returns number of frames.
        """
        value = self.settings.get('EventSelection', option_name)
        unit = self.settings.get('EventSelection', 'duration_unit')

        # get mean and stddev for the current position
        info = self.meta_data.get_timestamp_info(self.position)
        if unit == TimeConverter.FRAMES or info is None:
            result = value
        elif unit == TimeConverter.MINUTES:
            result = (value * 60.) / info[0]
        elif unit == TimeConverter.SECONDS:
            result = value / info[0]
        else:
            raise ValueError("Wrong unit '%s' specified." %unit)
        return int(round(result))

    def define_exp_features(self):
        features = {}
        for name in self.processing_channels:
            region_features = {}

            for region in MetaPluginManager().region_info.names[name.lower()]:
                if name is self.MERGED_CHANNEL:
                    continue
                # export all features extracted per regions
                if self.settings.get('Output', 'events_export_all_features') or \
                        self.settings.get('Output', 'export_track_data'):
                    # None means all features
                    region_features[region] = None
                # export selected features from settings
                else:
                    region_features[region] = \
                        self.settings.get('General',
                                          '%s_featureextraction_exportfeaturenames'
                                          % name.lower())

                features[name] = region_features

            # special case for merged channel
            if name is self.MERGED_CHANNEL:
                mftrs = list()
                for channel, region in self._channel_regions(name).iteritems():
                    if features[channel][region] is None:
                        mftrs = None
                    else:
                        for feature in features[channel][region]:
                            mftrs.append("_".join((channel, region, feature)))
                region_features[self._all_channel_regions[name].values()] = mftrs
                features[name] = region_features

        return features

    def export_object_counts(self):
        fname = join(self._statistics_dir, 'P%s__object_counts.txt' % self.position)

        ch_info = OrderedDict()
        for name, clf in self.classifiers.iteritems():
            names = clf.class_names.values()
            colors = [clf.hexcolors[n] for n in names]
            ch_info[name] = (clf.regions, names, colors)

        # if no classifier has been loaded, no counts can be exported.
        if len(ch_info) == 0:
            return

        self.timeholder.exportObjectCounts(fname, self.position, self.meta_data, ch_info)
        pplot_ymax = \
            self.settings.get('Output', 'export_object_counts_ylim_max')

        self.timeholder.exportPopulationPlots(ch_info, self._plots_dir,
                                              self.plate_id, self.position,
                                              ymax=pplot_ymax)



    def export_object_details(self):
        fname = join(self._statistics_dir,
                        'P%s__object_details.txt' % self.position)
        self.timeholder.exportObjectDetails(fname)

    def export_image_names(self):
        self.timeholder.exportImageFileNames(self._statistics_dir,
                                             self.position,
                                             self._imagecontainer._importer,
                                             self.ch_mapping)

    def export_full_tracks(self):
        odir = join(self._statistics_dir, 'full')
        exporter = EventExporter(self.meta_data)
        exporter.full_tracks(self.timeholder, self._tes.visitor_data,
                             self.position, odir)

    def export_graphviz(self, channel_name='Primary', region_name='primary'):
        filename = 'tracking_graph___P%s.dot' %self.position
        exporter = TrackExporter()
        exporter.graphviz_dot(join(self._statistics_dir, filename),
                              self._tracker)

        sample_holders = OrderedDict()
        for frame in self.timeholder.keys():
            channel = self.timeholder[frame][channel_name]
            sample_holders[frame] = channel.get_region(region_name)

        filename = join(self._statistics_dir, filename.replace('.dot', '_features.csv'))
        exporter.tracking_data(filename, sample_holders)

    def export_gallery_images(self):
        for ch_name in self.processing_channels:
            cutter_in = join(self._images_dir, ch_name.lower())

            if not isdir(cutter_in):
                self.logger.warning('directory not found (%s)' %cutter_in)
                self.logger.warning('can not write the gallery images')
            else:
                cutter_out = join(self._gallery_dir, ch_name.lower())
                self.logger.info("running Cutter for '%s'..." %ch_name)
                image_size = \
                    self.settings.get('Output', 'events_gallery_image_size')
                TrackGallery(self._tes.centers(),
                             cutter_in, cutter_out, self.position, size=image_size)
            # FIXME: be careful here. normally only raw images are
            #        used for the cutter and can be deleted
            #        afterwards
            shutil.rmtree(cutter_in, ignore_errors=True)

    def export_tracks_hdf5(self):
        """Save tracking data to hdf file"""
        self.logger.debug("--- serializing tracking start")
        self.timeholder.serialize_tracking(self._tracker.graph)
        self.logger.debug("--- serializing tracking ok")

    def export_events(self):
        """Export and save event selection data"""
        exporter = EventExporter(self.meta_data)
        # writes to the event folder
        odir = join(self._statistics_dir, "events")
        exporter.track_features(self.timeholder, self._tes.visitor_data,
                                self.export_features, self.position, odir)
        self.logger.debug("--- serializing events ok")

    def export_events_hdf5(self):
        # writes event data to hdf5
        self.timeholder.serialize_events(self._tes)

    def export_tc3(self):
        t_mean = self.meta_data.get_timestamp_info(self.position)[0]
        tu = TimeConverter(t_mean, TimeConverter.SECONDS)
        increment = self.settings('General', 'frameincrement')
        t_step = tu.sec2min(t_mean)*increment

        nclusters = self.settings.get('EventSelection', 'num_clusters')
        exporter = TC3Exporter(self._tes.tc3data, self._tc3_dir, nclusters,
                               t_step, TimeConverter.MINUTES, self.position)
        exporter()

    def export_classlabels(self):
        """Save classlabels of each object to the hdf file."""
        # function works for supervised and unuspervised case
        for channels in self.timeholder.itervalues():
            for chname, classifier in self.classifiers.iteritems():
                holder = channels[chname].get_region(classifier.regions)
                if classifier.feature_names is None:
                    # special for unsupervised case
                    classifier.feature_names = holder.feature_names
                self.timeholder.save_classlabels(channels[chname],
                                                 holder, classifier)

    def __call__(self):
        # include hdf5 file name in hdf5_options
        # perhaps timeholder might be a good place to read out the options
        # file does not have to exist to proceed
        hdf5_fname = join(self._hdf5_dir, '%s.ch5' % self.position)

        self.timeholder = TimeHolder(self.position, self._all_channel_regions,
                                     hdf5_fname,
                                     self.meta_data, self.settings,
                                     self._frames,
                                     self.plate_id,
                                     **self._hdf_options)

        self.settings.set_section('Tracking')
        self.setup_classifiers()

        # setup tracker
        if self.settings('Processing', 'tracking'):
            tropts = (self.settings('Tracking', 'tracking_maxobjectdistance'),
                      self.settings('Tracking', 'tracking_maxsplitobjects'),
                      self.settings('Tracking', 'tracking_maxtrackinggap'))
            self._tracker = Tracker(*tropts)

        stopwatch = StopWatch(start=True)
        ca = CellAnalyzer(timeholder=self.timeholder,
                          position = self.position,
                          create_images = True,
                          binning_factor = 1,
                          detect_objects = self.settings('Processing',
                                                         'objectdetection'))

        self.export_features = self.define_exp_features()
        n_images = self._analyze(ca)

        if n_images > 0:
            # invoke event selection
            if self.settings('Processing', 'eventselection') and \
                    self.settings('Processing', 'tracking'):

                evchannel = self.settings('EventSelection', 'eventchannel')
                region = self.classifiers[evchannel].regions
                if self.settings('EventSelection', 'unsupervised_event_selection'):
                    graph = self._tracker.graph
                elif  evchannel != PrimaryChannel.NAME or \
                        region != self.settings("Tracking", "region"):
                    graph = self._tracker.clone_graph(self.timeholder,
                                                      evchannel,
                                                      region)
                else:
                    graph = self._tracker.graph

                self._tes = self.setup_eventselection(graph)
                self.logger.debug("--- visitor start")
                self._tes.find_events()
                self.logger.debug("--- visitor ok")
                if self.is_aborted():
                    return 0 # number of processed images

            # save all the data of the position, no aborts from here on
            # want all processed data saved
            if self.settings('Output', 'export_object_counts') and \
                    self.settings('EventSelection', 'supervised_event_selection'):
                # no object counts in case of unsupervised event selection
                self.export_object_counts()
            if self.settings('Output', 'export_object_details'):
                self.export_object_details()
            if self.settings('Output', 'export_file_names'):
                self.export_image_names()

            if self.settings('Processing', 'tracking'):
                self.export_tracks_hdf5()
                self.update_status({'text': 'export events...'})

                if self.settings('Output', 'hdf5_include_events'):
                    self.export_events_hdf5()

                if self.settings('Output', "export_events"):
                    if self.settings('Processing', 'eventselection'):
                        self.export_events()
                    if self.settings('EventSelection', 'unsupervised_event_selection'):
                        self.export_tc3()

                if self.settings('Output', 'export_track_data'):
                    self.export_full_tracks()
                if self.settings('Output', 'export_tracking_as_dot'):
                    self.export_graphviz(channel_name =PrimaryChannel.NAME,\
                                          region_name =self._all_channel_regions[PrimaryChannel.NAME][PrimaryChannel.NAME])

            self.export_classlabels()

            self.update_status({'text': 'export events...',
                                'max': 1,
                                'progress': 1})

            # remove all features from all channels to free memory
            # for the generation of gallery images
            self.timeholder.purge_features()
            if self.settings.get('Output', 'events_export_gallery_images') and \
                    self.settings.get('Processing', 'eventselection'):
                self.export_gallery_images()

        try:
            intval = stopwatch.stop()/n_images*1000
        except ZeroDivisionError:
            pass
        else:
            self.logger.info(" - %d image sets analyzed, %3d ms per image set" %
                             (n_images, intval))

        self.touch_finished()
        self.clear()
        return n_images

    @property
    def hdf5_filename(self):
        return self.timeholder.hdf5_filename

    def touch_finished(self, times=None):
        """Writes an empty file to mark this position as finished"""
        fname = join(self._finished_dir, '%s__finished.txt' % self.position)
        with open(fname, "w") as f:
            os.utime(fname, times)

    def clear(self):
        # closes
        if self.timeholder is not None:
            self.timeholder.close_all()
        # close and remove handlers from logging object
        self.close()

    def _analyze(self, cellanalyzer):
        super(PositionAnalyzer, self)._analyze()
        n_images = 0
        stopwatch = StopWatch(start=True)
        crd = Coordinate(self.plate_id, self.position,
                         self._frames, list(set(self.ch_mapping.values())))

        minimal_effort = self.settings.get('Output', 'minimal_effort') and self.settings.get('Output', 'hdf5_reuse')

        for frame, channels in self._imagecontainer( \
            crd, interrupt_channel=True, interrupt_zslice=True):

            if self.is_aborted():
                self.clear()
                return 0
            else:
                txt = 'T %d (%d/%d)' %(frame, self._frames.index(frame)+1,
                                       len(self._frames))
                self.update_status({'progress': self._frames.index(frame)+1,
                                    'text': txt,
                                    'interval': stopwatch.interim()})

            stopwatch.reset(start=True)
            cellanalyzer.initTimepoint(frame)
            self.register_channels(cellanalyzer, channels)

            cellanalyzer.process()

            self.logger.info(" - Frame %d, cellanalyzer.process (ms): %3d" \
                             %(frame, stopwatch.interval()*1000))

            n_images += 1
            images = []

            if self.settings('Processing', 'tracking'):
                region = self.settings('Tracking', 'region')
                samples = self.timeholder[frame][PrimaryChannel.NAME].get_region(region)
                self._tracker.track_next_frame(frame, samples)

                if self.settings('Tracking', 'tracking_visualization'):
                    size = cellanalyzer.getImageSize(PrimaryChannel.NAME)
                    nframes = self.settings('Tracking', 'tracking_visualize_track_length')
                    radius = self.settings('Tracking', 'tracking_centroid_radius')
                    img_conn, img_split = self._tracker.render_tracks(
                        frame, size, nframes, radius)
                    images += [(img_conn, '#FFFF00', 1.0),
                               (img_split, '#00FFFF', 1.0)]

            self.logger.info(" - Frame %d, Tracking (ms): %3d" \
                             %(frame, stopwatch.interval()*1000))

            # can't cluster on a per frame basis
            if self.settings("EventSelection", "supervised_event_selection"):
                for clf in self.classifiers.itervalues():
                    cellanalyzer.classify_objects(clf)

            self.logger.info(" - Frame %d, Classification (ms): %3d" \
                             % (frame, stopwatch.interval()*1000))

            self.settings.set_section('General')
            # want emit all images at once
            if not minimal_effort:
                imgs = {}
                imgs.update(self.render_classification_images(cellanalyzer, images, frame))
                imgs.update(self.render_contour_images(cellanalyzer, images, frame))
                msg = 'PL %s - P %s - T %05d' %(self.plate_id, self.position, frame)
                self.set_image(imgs, msg, 50)

                if self.settings('Output', 'rendering_channel_gallery'):
                    self.render_channel_gallery(cellanalyzer, frame)

                if self.settings('Output', 'rendering_labels_discwrite'):
                    cellanalyzer.exportLabelImages(self._labels_dir)

            cellanalyzer.purge(features=self.export_features)
            self.logger.info(" - Frame %d, rest (ms): %3d" \
                                 %(frame, stopwatch.interval()*1000))
            self.logger.info(" - Frame %d, duration (ms): %3d" \
                                 %(frame, stopwatch.interim()*1000))


        return n_images

    def render_channel_gallery(self, cellanalyzer, frame):
        for channel in cellanalyzer.virtual_channels.itervalues():
            chgal = ChannelGallery(channel, frame, self._channel_gallery_dir)
            chgal.make_gallery()

    def render_contour_images(self, ca, images, frame):
        images_ = dict()
        for region, render_par in self.settings.get2('rendering').iteritems():
            out_dir = join(self._images_dir, region)
            write = self.settings('Output', 'rendering_contours_discwrite')

            if region not in self.CHANNELS.keys():
                img, _ = ca.render(out_dir, dctRenderInfo=render_par,
                                       writeToDisc=write, images=images)


                images_[region] = img
            # gallery images are treated differenty
            else:
                ca.render(out_dir, dctRenderInfo=render_par, writeToDisc=True)
        return images_

    def render_classification_images(self, cellanalyzer, images, frame):
         images_ = dict()
         for region, render_par in self.settings.get2('rendering_class').iteritems():
            out_images = join(self._images_dir, region)
            write = self.settings('Output', 'rendering_class_discwrite')
            image, _ = cellanalyzer.render(out_images,
                                                 dctRenderInfo=render_par,
                                                 writeToDisc=write,
                                                 images=images)
            images_[region] = image
         return images_
示例#4
0
文件: position.py 项目: imcf/cecog
class PositionAnalyzer(PositionCore):
    def __init__(self, *args, **kw):
        super(PositionAnalyzer, self).__init__(*args, **kw)

        if not self.has_timelapse:
            self.settings.set('Processing', 'tracking', False)

        self._makedirs()
        self.add_file_handler(join(self._log_dir, "%s.log" % self.position),
                              self._lvl.DEBUG)

    def _makedirs(self):
        assert isinstance(self.position, basestring)
        assert isinstance(self._out_dir, basestring)

        self._analyzed_dir = join(self._out_dir, "analyzed")
        if self.has_timelapse:
            self._position_dir = join(self._analyzed_dir, self.position)
        else:
            self._position_dir = self._analyzed_dir

        odirs = (self._analyzed_dir, join(self._out_dir, "log"),
                 join(self._out_dir, "log",
                      "_finished"), join(self._out_dir,
                                         "hdf5"), join(self._out_dir, "plots"),
                 join(self._position_dir,
                      "statistics"), join(self._position_dir, "gallery"),
                 join(self._position_dir,
                      "channel_gallery"), join(self._position_dir, "images"),
                 join(self._position_dir, "images", "_labels"))

        for odir in odirs:
            try:
                makedirs(odir)
            except os.error:  # no permissions
                self.logger.error("mkdir %s: failed" % odir)
            else:
                self.logger.info("mkdir %s: ok" % odir)
            setattr(self, "_%s_dir" % basename(odir.lower()).strip("_"), odir)

    def setup_classifiers(self):
        sttg = self.settings
        # processing channel, color channel
        for p_channel, c_channel in self.ch_mapping.iteritems():
            self.settings.set_section('Processing')
            if sttg.get2(self._resolve_name(p_channel, 'classification')):
                sttg.set_section('Classification')
                clf = CommonClassPredictor(
                    clf_dir=sttg.get2(
                        self._resolve_name(p_channel,
                                           'classification_envpath')),
                    name=p_channel,
                    channels=self._channel_regions(p_channel),
                    color_channel=c_channel)
                clf.importFromArff()
                clf.loadClassifier()
                self.classifiers[p_channel] = clf

    def _convert_tracking_duration(self, option_name):
        """Converts a tracking duration according to the unit and the
        mean time-lapse of the current position.
        Returns number of frames.
        """
        value = self.settings.get(SECTION_NAME_TRACKING, option_name)
        unit = self.settings.get(SECTION_NAME_TRACKING,
                                 'tracking_duration_unit')

        # get mean and stddev for the current position
        info = self.meta_data.get_timestamp_info(self.position)
        if unit == TRACKING_DURATION_UNIT_FRAMES or info is None:
            result = value
        elif unit == TRACKING_DURATION_UNIT_MINUTES:
            result = (value * 60.) / info[0]
        elif unit == TRACKING_DURATION_UNIT_SECONDS:
            result = value / info[0]
        else:
            raise ValueError("Wrong unit '%s' specified." % unit)
        return int(round(result))

    @property
    def _es_options(self):
        transitions = eval(self.settings.get2('tracking_labeltransitions'))
        if not isinstance(transitions[0], tuple):
            transitions = (transitions, )
        evopts = {
            'transitions':
            transitions,
            'backward_labels':
            map(int,
                self.settings.get2('tracking_backwardlabels').split(',')),
            'forward_labels':
            map(int,
                self.settings.get2('tracking_forwardlabels').split(',')),
            'backward_check':
            self._convert_tracking_duration('tracking_backwardCheck'),
            'forward_check':
            self._convert_tracking_duration('tracking_forwardCheck'),
            'backward_range':
            self._convert_tracking_duration('tracking_backwardrange'),
            'forward_range':
            self._convert_tracking_duration('tracking_forwardrange'),
            'backward_range_min':
            self.settings.get2('tracking_backwardrange_min'),
            'forward_range_min':
            self.settings.get2('tracking_forwardrange_min'),
            'max_in_degree':
            self.settings.get2('tracking_maxindegree'),
            'max_out_degree':
            self.settings.get2('tracking_maxoutdegree')
        }
        return evopts

    def define_exp_features(self):
        features = {}
        for name in self.processing_channels:
            region_features = {}
            for region in REGION_INFO.names[name.lower()]:
                # export all features extracted per regions
                if self.settings.get('Output', 'events_export_all_features') or \
                        self.settings.get('Output', 'export_track_data'):
                    # None means all features
                    region_features[region] = None
                # export selected features from settings
                else:
                    region_features[region] = \
                        self.settings.get('General',
                                          '%s_featureextraction_exportfeaturenames'
                                          % name.lower())
                features[name] = region_features
        return features

    def export_object_counts(self):
        fname = join(self._statistics_dir,
                     'P%s__object_counts.txt' % self.position)

        # at least the total count for primary is always exported
        ch_info = OrderedDict([('Primary', ('primary', [], []))])
        for name, clf in self.classifiers.iteritems():
            names = clf.class_names.values()
            colors = [clf.hexcolors[n] for n in names]
            ch_info[name] = (clf.regions, names, colors)

        self.timeholder.exportObjectCounts(fname, self.position,
                                           self.meta_data, ch_info)
        pplot_ymax = \
            self.settings.get('Output', 'export_object_counts_ylim_max')

        # plot only for primary channel so far!
        self.timeholder.exportPopulationPlots(fname, self._plots_dir,
                                              self.position, self.meta_data,
                                              ch_info['Primary'], pplot_ymax)

    def export_object_details(self):
        fname = join(self._statistics_dir,
                     'P%s__object_details.txt' % self.position)
        self.timeholder.exportObjectDetails(fname, excel_style=False)
        fname = join(self._statistics_dir,
                     'P%s__object_details_excel.txt' % self.position)
        self.timeholder.exportObjectDetails(fname, excel_style=True)

    def export_image_names(self):
        self.timeholder.exportImageFileNames(self._statistics_dir,
                                             self.position,
                                             self._imagecontainer._importer,
                                             self.ch_mapping)

    def export_full_tracks(self):
        odir = join(self._statistics_dir, 'full')
        exporter = EventExporter(self.meta_data)
        exporter.full_tracks(self.timeholder, self._tes.visitor_data,
                             self.position, odir)

    def export_graphviz(self, channel_name='Primary', region_name='primary'):
        filename = 'tracking_graph___P%s.dot' % self.position
        exporter = TrackExporter()
        exporter.graphviz_dot(join(self._statistics_dir, filename),
                              self._tracker)

        sample_holders = OrderedDict()
        for frame in self.timeholder.keys():
            channel = self.timeholder[frame][channel_name]
            sample_holders[frame] = channel.get_region(region_name)

        filename = join(self._statistics_dir,
                        filename.replace('.dot', '_features.csv'))
        exporter.tracking_data(filename, sample_holders)

    def export_gallery_images(self):
        for ch_name in self.processing_channels:
            cutter_in = join(self._images_dir, ch_name)
            if isdir(cutter_in):
                cutter_out = join(self._gallery_dir, ch_name)
                self.logger.info("running Cutter for '%s'..." % ch_name)
                image_size = \
                    self.settings.get('Output', 'events_gallery_image_size')
                EventGallery(self._tes,
                             cutter_in,
                             self.position,
                             cutter_out,
                             self.meta_data,
                             oneFilePerTrack=True,
                             size=(image_size, image_size))
            # FIXME: be careful here. normally only raw images are
            #        used for the cutter and can be deleted
            #        afterwards
            shutil.rmtree(cutter_in, ignore_errors=True)

    def export_tracks_hdf5(self):
        """Save tracking data to hdf file"""
        self.logger.debug("--- serializing tracking start")
        self.timeholder.serialize_tracking(self._tes.graph)
        self.logger.debug("--- serializing tracking ok")

    def export_events(self):
        """Export and save event selceciton data"""
        exporter = EventExporter(self.meta_data)
        # writes to the event folder
        odir = join(self._statistics_dir, "events")
        exporter.track_features(self.timeholder, self._tes.visitor_data,
                                self.export_features, self.position, odir)
        self.logger.debug("--- visitor analysis ok")
        # writes event data to hdf5
        self.timeholder.serialize_events(self._tes)
        self.logger.debug("--- serializing events ok")

    def __call__(self):
        # include hdf5 file name in hdf5_options
        # perhaps timeholder might be a good placke to read out the options
        # fils must not exist to proceed
        hdf5_fname = join(self._hdf5_dir, '%s.ch5' % self.position)

        self.timeholder = TimeHolder(self.position, self._all_channel_regions,
                                     hdf5_fname, self.meta_data, self.settings,
                                     self._frames, self.plate_id,
                                     **self._hdf_options)

        self.settings.set_section('Tracking')
        # setup tracker
        if self.settings.get('Processing', 'tracking'):
            region = self.settings.get('Tracking', 'tracking_regionname')
            tropts = (self.settings.get('Tracking',
                                        'tracking_maxobjectdistance'),
                      self.settings.get('Tracking',
                                        'tracking_maxsplitobjects'),
                      self.settings.get('Tracking', 'tracking_maxtrackinggap'))
            self._tracker = Tracker(*tropts)
            self._tes = EventSelection(self._tracker.graph, **self._es_options)

        stopwatch = StopWatch(start=True)
        ca = CellAnalyzer(timeholder=self.timeholder,
                          position=self.position,
                          create_images=True,
                          binning_factor=1,
                          detect_objects=self.settings.get(
                              'Processing', 'objectdetection'))

        self.setup_classifiers()
        self.export_features = self.define_exp_features()
        n_images = self._analyze(ca)

        if n_images > 0:
            # invoke event selection
            if self.settings.get('Processing', 'tracking_synchronize_trajectories') and \
                    self.settings.get('Processing', 'tracking'):
                self.logger.debug("--- visitor start")
                self._tes.find_events()
                self.logger.debug("--- visitor ok")
                if self.is_aborted():
                    return 0  # number of processed images

            # save all the data of the position, no aborts from here on
            # want all processed data saved
            if self.settings.get('Output', 'export_object_counts'):
                self.export_object_counts()
            if self.settings.get('Output', 'export_object_details'):
                self.export_object_details()
            if self.settings.get('Output', 'export_file_names'):
                self.export_image_names()

            if self.settings.get('Processing', 'tracking'):
                self.export_tracks_hdf5()
                self.update_status({'text': 'export events...'})
                if self.settings.get('Processing',
                                     'tracking_synchronize_trajectories'):
                    self.export_events()
                if self.settings.get('Output', 'export_track_data'):
                    self.export_full_tracks()
                if self.settings.get('Output', 'export_tracking_as_dot'):
                    self.export_graphviz()

            self.update_status({
                'text': 'export events...',
                'max': 1,
                'progress': 1
            })

            # remove all features from all channels to free memory
            # for the generation of gallery images
            self.timeholder.purge_features()
            if self.settings.get('Output', 'events_export_gallery_images'):
                self.export_gallery_images()

        try:
            intval = stopwatch.stop() / n_images * 1000
        except ZeroDivisionError:
            pass
        else:
            self.logger.info(
                " - %d image sets analyzed, %3d ms per image set" %
                (n_images, intval))

        self.touch_finished()
        #        self.clear()
        return n_images

    @property
    def hdf5_filename(self):
        return self.timeholder.hdf5_filename

    def touch_finished(self, times=None):
        """Writes an empty file to mark this position as finished"""
        fname = join(self._finished_dir, '%s__finished.txt' % self.position)
        with open(fname, "w") as f:
            os.utime(fname, times)

    def clear(self):
        # closes hdf5
        self.timeholder.close_all()

    def _analyze(self, cellanalyzer):
        super(PositionAnalyzer, self)._analyze()
        n_images = 0
        stopwatch = StopWatch(start=True)
        crd = Coordinate(self.plate_id, self.position, self._frames,
                         list(set(self.ch_mapping.values())))

        for frame, channels in self._imagecontainer( \
            crd, interrupt_channel=True, interrupt_zslice=True):

            if self.is_aborted():
                self.clear()
                return 0
            else:
                txt = 'T %d (%d/%d)' % (frame, self._frames.index(frame) + 1,
                                        len(self._frames))
                self.update_status({
                    'progress': self._frames.index(frame) + 1,
                    'text': txt,
                    'interval': stopwatch.interim()
                })

            stopwatch.reset(start=True)
            cellanalyzer.initTimepoint(frame)
            self.register_channels(cellanalyzer, channels)

            cellanalyzer.process()
            n_images += 1
            images = []

            if self.settings.get('Processing', 'tracking'):
                region = self.settings.get('Tracking', 'tracking_regionname')
                samples = self.timeholder[frame][
                    PrimaryChannel.NAME].get_region(region)
                self._tracker.track_next_frame(frame, samples)

                if self.settings.get('Tracking', 'tracking_visualization'):
                    size = cellanalyzer.getImageSize(PrimaryChannel.NAME)
                    nframes = self.settings.get(
                        'Tracking', 'tracking_visualize_track_length')
                    radius = self.settings.get('Tracking',
                                               'tracking_centroid_radius')
                    img_conn, img_split = self._tracker.render_tracks(
                        frame, size, nframes, radius)
                    images += [(img_conn, '#FFFF00', 1.0),
                               (img_split, '#00FFFF', 1.0)]

            for clf in self.classifiers.itervalues():
                cellanalyzer.classify_objects(clf)

            ##############################################################
            # FIXME - part for browser
            if not self._myhack is None:
                self.render_browser(cellanalyzer)
            ##############################################################

            self.settings.set_section('General')
            self.render_classification_images(cellanalyzer, images, frame)
            self.render_contour_images(cellanalyzer, images, frame)

            if self.settings.get('Output', 'rendering_channel_gallery'):
                self.render_channel_gallery(cellanalyzer, frame)

            if self.settings.get('Output', 'rendering_labels_discwrite'):
                cellanalyzer.exportLabelImages(self._labels_dir)

            self.logger.info(" - Frame %d, duration (ms): %3d" \
                                 %(frame, stopwatch.interim()*1000))
            cellanalyzer.purge(features=self.export_features)

        return n_images

    def render_channel_gallery(self, cellanalyzer, frame):
        for channel in cellanalyzer.virtual_channels.itervalues():
            chgal = ChannelGallery(channel, frame, self._channel_gallery_dir)
            chgal.make_gallery()

    def render_contour_images(self, ca, images, frame):
        for region, render_par in self.settings.get2('rendering').iteritems():
            out_dir = join(self._images_dir, region)
            write = self.settings.get('Output', 'rendering_contours_discwrite')

            if region not in self.CHANNELS.keys():
                img, fname = ca.render(out_dir,
                                       dctRenderInfo=render_par,
                                       writeToDisc=write,
                                       images=images)
                msg = 'PL %s - P %s - T %05d' % (self.plate_id, self.position,
                                                 frame)
                self.set_image(img, msg, fname, region, 50)
            # gallery images are treated differenty
            else:
                ca.render(out_dir, dctRenderInfo=render_par, writeToDisc=True)

    def render_classification_images(self, cellanalyzer, images, frame):
        for region, render_par in self.settings.get2(
                'rendering_class').iteritems():
            out_images = join(self._images_dir, region)
            write = self.settings.get('Output', 'rendering_class_discwrite')
            img_rgb, fname = cellanalyzer.render(out_images,
                                                 dctRenderInfo=render_par,
                                                 writeToDisc=write,
                                                 images=images)

            msg = 'PL %s - P %s - T %05d' % (self.plate_id, self.position,
                                             frame)
            self.set_image(img_rgb, msg, fname, region, 50)

    def render_browser(self, cellanalyzer):
        d = {}
        for name in cellanalyzer.get_channel_names():
            channel = cellanalyzer.get_channel(name)
            d[channel.strChannelId] = channel.meta_image.image
            self._myhack.show_image(d)

        channel_name, region_name = self._myhack._object_region
        channel = cellanalyzer.get_channel(channel_name)
        if channel.has_region(region_name):
            region = channel.get_region(region_name)
            coords = {}
            for obj_id, obj in region.iteritems():
                coords[obj_id] = obj.crack_contour
            self._myhack.set_coords(coords)