예제 #1
0
    def track_tails(self, make_video=False):
        """ processes the video and locates the tails """
        # initialize the video
        self.load_video(make_video)
        
        # process first _frame to find objects
        tails = self.process_first_frame()
        
        # initialize the result structure
        tail_trajectories = collections.defaultdict(dict)

        # iterate through all frames
        iterator = display_progress(self.video)
        for self.frame_id, self._frame in enumerate(iterator, self.frame_start):
            self._frame_cache = {} #< delete cache per _frame
            if make_video:
                self.set_video_background(tails)

            # adapt the object outlines
            tails = self.adapt_tail_contours(tails)
            
            # store tail data in the result
            for tail_id, tail in enumerate(tails):
                tail_trajectories[tail_id][self.frame_id] = tail
            
            # update the debug output
            if make_video:
                self.update_video_output(tails, show_measurement_line=False)
            
        # save the data and close the videos
        self.result['tail_trajectories'] = tail_trajectories
        self.save_result()
        self.close()
예제 #2
0
def reduce_video(video, function, initial_value=None):
    """ applies function to consecutive frames """
    result = initial_value
    for frame in display_progress(video):
        if result is None:
            result = frame
        else:
            result = function(frame, result)
    return result
예제 #3
0
def measure_mean(video):
    """
    measures the mean of each movie pixel over time
    """
    mean = np.zeros(video.shape[1:])
 
    for n, frame in enumerate(display_progress(video)):
        mean = mean*n/(n + 1) + frame/(n + 1)
 
    return mean
예제 #4
0
    def _iterate_over_video(self, video):
        """ internal function doing the heavy lifting by iterating over the video """
        
        # load data from previous passes
        ground_profile = self.data['pass2/ground_profile']
        background_frame_offset = self.data['pass1/video/background_frame_offset']

        # create the video iterator with preprocessing that will be done in a
        # separate thread to speed up computations
        video_iter = VideoPreprocessor(
            video, functions={'gradient_strength': self.get_gradient_strenght},
            use_threads=self.params['use_threads']
        )
        video_iter = display_progress(video_iter)


        # iterate over the video and analyze it
        for background_id, data in enumerate(video_iter):
            
            # extract the images from the preprocessed data            
            frame = data['raw']
            gradient_strength = data['gradient_strength']
            
            # calculate _frame id in the original video
            self.frame_id = (background_frame_offset 
                             + background_id * self.params['output/video/period']) 
            
            # copy _frame to debug video
            if 'video' in self.debug:
                self.debug['video'].set_frame(frame, copy=False)
            
            # retrieve data for current _frame
            self.ground = ground_profile.get_ground_profile(self.frame_id)

            # write the mouse trail to the mouse mask
            self.update_mouse_mask()

            # find the changes in the background
            if self.params['burrows/enabled_pass4']:
                self.find_burrows_using_active_contour(frame, gradient_strength)

            # store some debug information
            self.debug_process_frame(frame)
            
            if background_id % 1000 == 0:
                self.logger.debug('Analyzed _frame %d', self.frame_id)
예제 #5
0
def measure_mean_std(video):
    """
    measures the mean and the standard deviation of each movie pixel over time
    Uses https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Incremental_algorithm
    """
    mean = np.zeros(video.shape[1:])
    M2 = np.zeros(video.shape[1:])
    
    for n, frame in enumerate(display_progress(video)):
        delta = frame - mean
        mean = mean + delta/(n + 1)
        M2 = M2 + delta*(frame - mean)
 
    if (n < 2):
        return frame, 0

    return mean, np.sqrt(M2/n)
예제 #6
0
def determine_average_frame_brightness(video_file_or_pattern,
                                       output_hdf5_file=None):
    """ iterates a video and determines its intensity, which will be stored
    in a hdf5 file, if the respective file name is given"""
    # read video data
    with closing(load_any_video(video_file_or_pattern)) as video:
        brightness = np.empty(video.frame_count, np.double)
        for k, frame in enumerate(display_progress(video)):
            brightness[k] = frame.mean()
        # restrict the result to the number of actual frames read
        if k < video.frame_count:
            brightness = brightness[:k + 1]

    # write brightness data
    if output_hdf5_file:
        with h5py.File(output_hdf5_file, "w") as fd:
            fd.create_dataset("brightness", data=brightness)

    return brightness
예제 #7
0
def determine_average_frame_brightness(video_file_or_pattern,
                                       output_hdf5_file=None):
    """ iterates a video and determines its intensity, which will be stored
    in a hdf5 file, if the respective file name is given"""
    # read video data
    with closing(load_any_video(video_file_or_pattern)) as video:
        brightness = np.empty(video.frame_count, np.double)
        for k, frame in enumerate(display_progress(video)):
            brightness[k] = frame.mean()
        # restrict the result to the number of actual frames read
        if k < video.frame_count:
            brightness = brightness[:k + 1]

    # write brightness data
    if output_hdf5_file:
        with h5py.File(output_hdf5_file, "w") as fd:
            fd.create_dataset("brightness", data=brightness)
            
    return brightness
예제 #8
0
    def _iterate_over_video(self, video):
        """ internal function doing the heavy lifting by iterating over the video """

        # load data from previous passes
        mouse_track = self.data['pass2/mouse_trajectory']
        ground_profile = self.data['pass2/ground_profile']
        frame_offset = self.result['video/frames'][0]
        if frame_offset is None:
            frame_offset = 0

        # iterate over the video and analyze it
        for self.frame_id, frame in enumerate(display_progress(video),
                                              frame_offset):

            # adapt the background to current _frame
            adaptation_rate = self.params['background/adaptation_rate']
            self.background += adaptation_rate * (frame - self.background)

            # copy _frame to debug video
            if 'video' in self.debug:
                self.debug['video'].set_frame(frame, copy=False)

            # retrieve data for current _frame
            try:
                self.mouse_pos = mouse_track.pos[self.frame_id, :]
            except IndexError:
                # Sometimes the mouse trail has not been calculated till the end
                self.mouse_pos = (np.nan, np.nan)
            self.ground = ground_profile.get_ground_profile(self.frame_id)

            if self.params['burrows/enabled_pass3']:
                # find the burrow from the mouse trail
                self.find_burrows()

            # find out where the mouse currently is
            self.classify_mouse_state(mouse_track)

            # store some information in the debug dictionary
            self.debug_process_frame(frame, mouse_track)

            if self.frame_id % 1000 == 0:
                self.logger.debug('Analyzed _frame %d', self.frame_id)
예제 #9
0
    def _iterate_over_video(self, video):
        """ internal function doing the heavy lifting by iterating over the video """
        
        # load data from previous passes
        mouse_track = self.data['pass2/mouse_trajectory']
        ground_profile = self.data['pass2/ground_profile']
        frame_offset = self.result['video/frames'][0]
        if frame_offset is None:
            frame_offset = 0

        # iterate over the video and analyze it
        for self.frame_id, frame in enumerate(display_progress(video), frame_offset):
            
            # adapt the background to current _frame 
            adaptation_rate = self.params['background/adaptation_rate']
            self.background += adaptation_rate*(frame - self.background)
            
            # copy _frame to debug video
            if 'video' in self.debug:
                self.debug['video'].set_frame(frame, copy=False)
            
            # retrieve data for current _frame
            try:
                self.mouse_pos = mouse_track.pos[self.frame_id, :]
            except IndexError:
                # Sometimes the mouse trail has not been calculated till the end
                self.mouse_pos = (np.nan, np.nan)
            self.ground = ground_profile.get_ground_profile(self.frame_id)

            if self.params['burrows/enabled_pass3']:
                # find the burrow from the mouse trail
                self.find_burrows()

            # find out where the mouse currently is
            self.classify_mouse_state(mouse_track)
            
            # store some information in the debug dictionary
            self.debug_process_frame(frame, mouse_track)
            
            if self.frame_id % 1000 == 0:
                self.logger.debug('Analyzed _frame %d', self.frame_id)
예제 #10
0
 def copy(self, dtype=np.uint8, disp=True):
     """
     Creates a copy of the current video and returns a VideoMemory instance.
     """
     # prevent circular import by lazy importing
     from .memory import VideoMemory
     
     logger.debug('Copy a video stream and store it in memory')
     
     # copy the data into a numpy array
     data = np.empty(self.shape, dtype)
     
     if disp:
         iterator = display_progress(self)
     else:
         iterator = self
     
     for k, val in enumerate(iterator):
         data[k, ...] = val
         
     # construct the memory object without copying the data
     return VideoMemory(data, fps=self.fps, copy_data=False)
예제 #11
0
    def copy(self, dtype=np.uint8, disp=True):
        """
        Creates a copy of the current video and returns a VideoMemory instance.
        """
        # prevent circular import by lazy importing
        from .memory import VideoMemory

        logger.debug('Copy a video stream and store it in memory')

        # copy the data into a numpy array
        data = np.empty(self.shape, dtype)

        if disp:
            iterator = display_progress(self)
        else:
            iterator = self

        for k, val in enumerate(iterator):
            data[k, ...] = val

        # construct the memory object without copying the data
        return VideoMemory(data, fps=self.fps, copy_data=False)
예제 #12
0
    def do_linescans(self, make_video=True):
        """ do the line scans for all current_tails """
        # initialize the video
        self.load_video(make_video)
        self.load_result()
        
        # load the previously track tail data
        try:
            tail_trajectories = self.result['tail_trajectories']
        except KeyError:
            raise ValueError('Tails have to be tracked before line scans can '
                             'be done.')
        current_tails = None
            
        # collect all line scans in a structure
        linescans = collections.defaultdict(list)
            
        # iterate through all frames and collect the line scans
        iterator = display_progress(self.video)
        for self.frame_id, self._frame in enumerate(iterator, self.frame_start):
            self._frame_cache = {} #< delete cache per _frame
            if make_video:
                self.set_video_background(current_tails)

            # do the line scans in each object
            current_tails = [] 
            for tail_id, tail_trajectory in tail_trajectories.iteritems():
                try:
                    tail = tail_trajectory[self.frame_id]
                except KeyError:
                    logging.debug('Could not find any information on tails '
                                  'for _frame %d', self.frame_id)
                    break
                # get the line scan left and right of the centerline
                linescan_data= self.measure_single_tail(self._frame, tail)
                # append it to the temporary structure
                linescans[tail_id].append(linescan_data)
                current_tails.append(tail) #< safe for video output
            
            # update the debug output
            if make_video:
                self.update_video_output(current_tails,
                                         show_measurement_line=True)
         
        # create the kymographs from the line scans
        kymographs = collections.defaultdict(dict)
        # iterate over all tails found in the movie
        for tail_id, linescan_trajectory in linescans.iteritems():
            # transpose data to have data for each side of the center line  
            line_scans = zip(*linescan_trajectory)
            
            # iterate over all line scans (ventral and dorsal)
            for side_id, data in enumerate(line_scans):
                kymograph = Kymograph(data)
                side = tail.line_names[side_id]
                kymographs[tail_id][side] = kymograph
                            
        # save the data and close the videos
        self.result['kymographs'] = kymographs 
        self.save_result()
        self.close()
예제 #13
0
    def produce_video(self):
        """ prepares everything for the debug output """
        # load parameters for video output        
        video_extension = self.params['output/video/extension']
        video_codec = self.params['output/video/codec']
        video_bitrate = self.params['output/video/bitrate']
        
        if self.video is None:
            self.load_video()
        
        filename = self.get_filename('video' + video_extension, 'video')
        video = VideoComposer(filename, size=self.video.size,
                              fps=self.video.fps, is_color=True,
                              output_period=self.params['output/output_period'],
                              codec=video_codec, bitrate=video_bitrate)
        
        mouse_track = self.data['pass2/mouse_trajectory']
        ground_profile = self.data['pass2/ground_profile']
        
        self.logger.info('Pass 2 - Start producing final video with %d frames', len(self.video))
        
        # we used the first frame to determine the cage dimensions in the first pass
        source_video = self.video[1:]
        
        track_first = 0
        tracks = self.data['pass1/objects/tracks']
        tracks.sort(key=lambda track: track.start)
        burrow_tracks = self.data['pass1/burrows/tracks']
        
        for frame_id, frame in enumerate(display_progress(source_video)):
            # set real video as background
            video.set_frame(frame)
        
            # plot the ground profile
            ground_line = ground_profile.get_groundline(frame_id)
            video.add_line(ground_line, is_closed=False,
                              mark_points=False, color='y')

            # indicate burrow centerline
            for burrow in burrow_tracks.find_burrows(frame_id):
                ground = GroundProfile(ground_line)
                video.add_line(burrow.get_centerline(ground),
                                  'k', is_closed=False, width=2)
        
            # indicate all moving objects
            # find the first track which is still active
            while tracks[track_first].end < frame_id:
                track_first += 1
            # draw all tracks that are still active
            for track in tracks[track_first:]:
                if track.start > frame_id:
                    # this is the last track that can possibly be active
                    break
                elif track.start <= frame_id <= track.end:
                    video.add_circle(track.get_pos(frame_id),
                                     self.params['mouse/model_radius'],
                                     '0.5', thickness=1)

            # indicate the mouse position
            if np.all(np.isfinite(mouse_track.pos[frame_id])):
                video.add_circle(mouse_track.pos[frame_id],
                                 self.params['mouse/model_radius'],
                                 'w', thickness=2)
                    
            
#                 # add additional debug information
            
            video.add_text(str(frame_id), (20, 20), anchor='top')   
#                 video.add_text(str(self.frame_id/self.fps), (20, 20), anchor='top')   
                
        video.close()
def make_cropped_video(result_file, output_video=None,
                       display='{time} [{frame}]', scale_bar=True,
                       border_buffer_cm=0, frame_compression=1,
                       time_duration=None, progress=True):
    """ function that crops a video to an antfarm.
    
    `result_file` is the file where the results from the video analysis are
        stored. This is usually a *.yaml file
    `output_video` denotes the filename where the result video should be
        written to.
    `display` determines what information is displayed. There are several
        variables that would be replaced by data:
            {time} the current time stamp
            {frame} the current frame number
    `scale_bar` determines whether a scale bar is shown
    `border_buffer_cm` sets the extra space (in units of cm) around the cropping
        rectangle that is included in the analysis
    `frame_compression` sets the compression factor that determines how many
        frames are dropped compared to the original video
    `time_duration` sets the maximal number of seconds the video is supposed to
        last. Additional frames will not be written.
    `progress` flag that determines whether the progress is displayed
    """
    logging.info('Analyze video `%s`', result_file)
    
    # load the respective result file 
    analyzer = load_result_file(result_file)

    # load the full original video
    video_info = analyzer.load_video(frames=(0, None))
    
    # crop the video to the cage
    video_input = analyzer.video
    cropping_cage = analyzer.data['pass1/video/cropping_cage']
    
    border_buffer_px = int(border_buffer_cm / analyzer.length_scale_mag)
    
    # change rectangle size if necessary
    if border_buffer_px != 0:
        cropping_rect = Rectangle.from_list(cropping_cage)
        video_rect = Rectangle(0, 0, video_input.width - 1,
                               video_input.height - 1)
        
        cropping_rect.buffer(border_buffer_px)
        cropping_rect.intersect(video_rect)
        cropping_cage = cropping_rect.to_list()
    
    if cropping_cage:
        # size_alignment=2 makes sure that the width and height are even numbers 
        video_input = FilterCrop(video_input, rect=cropping_cage,
                                 size_alignment=2)
        
    if frame_compression is not None and frame_compression != 1:
        video_input = FilterDropFrames(video_input,
                                       compression=frame_compression)
        
    if time_duration is not None:
        index_max = int(time_duration * video_input.fps)
        video_input = video_input[:index_max]
    
    # determine the filename of the output video
    if output_video is None:
        # determine the complete filename automatically
        movie_ext = analyzer.params['output/video/extension']
        filename = 'cropped'
        output_video = analyzer.get_filename(filename + movie_ext, 'video')
        
    elif '.' not in output_video:
        # determine the extension automatically
        movie_ext = analyzer.params['output/video/extension']
        output_video = output_video + movie_ext
    
    logging.info('Write output to `%s`', output_video)
    
    # create the video writer
    video_codec = analyzer.params['output/video/codec']
    video_bitrate = analyzer.params['output/video/crop_bitrate']
    if video_bitrate is None:
        video_bitrate = analyzer.params['output/video/bitrate']
    fps = video_input.fps
    video_output = VideoComposer(
        output_video, size=video_input.size, fps=fps,
        is_color=video_input.is_color, codec=video_codec, bitrate=video_bitrate,
    )
    
    # time label position
    label_pos = video_input.width // 2, 30

    # calculate size of scale bar and the position of its label
    pixel_size_cm = analyzer.data['pass2/pixel_size_cm']
    scale_bar_size_cm = 10
    scale_bar_size_px = np.round(scale_bar_size_cm / pixel_size_cm)
    scale_bar_rect = Rectangle(30, 50, scale_bar_size_px, 5)
    scale_bar_pos = (30 + scale_bar_size_px//2, 30)
    
    if progress:
        video_input = display_progress(video_input)
    
    for frame_id, frame in enumerate(video_input):
        video_output.set_frame(frame, copy=True)

        if scale_bar:
            # show a scale bar
            video_output.add_rectangle(scale_bar_rect, width=-1)
            video_output.add_text(str('%g cm' % scale_bar_size_cm),
                                  scale_bar_pos, color='w',
                                  anchor='upper center')

        # gather data about this frame
        frame_data = {'frame': frame_id}
        
        # calculate time stamp
        time_secs, time_frac = divmod(frame_id, fps)
        time_msecs = int(1000 * time_frac / fps)
        dt = datetime.timedelta(seconds=time_secs,
                                milliseconds=time_msecs)
        frame_data['time'] = str(dt)
            
        # output the display data
        if display:
            display_text = display.format(**frame_data)
            video_output.add_text(display_text, label_pos, color='w',
                                  anchor='upper center')

    # show summary
    frames_total = video_info['frames'][1] - video_info['frames'][0]
    frames_written = video_output.frames_written
    logging.info('%d (%d%%) of %d frames written', frames_written,
                 100 * frames_written // frames_total, frames_total)
    
    # close and finalize video
    try:
        video_output.close()
    except IOError:
        logging.exception('Error while writing out the debug video `%s`',
                          video_output)
        
def make_underground_video(result_file, output_video=None,
                           display='{time} [{frame}]', scale_bar=True,
                           min_duration=60, blank_duration=5,
                           bouts_slice=slice(None, None), video_part=None):
    """ main routine of the program
    `result_file` is the file where the results from the video analysis are
        stored. This is usually a *.yaml file
    `output_video` denotes the filename where the result video should be
        written to.
    `display` determines what information is displayed. There are several
        variables that would be replaced by data:
            {time} the current time stamp
            {frame} the current frame number
    `scale_bar` determines whether a scale bar is shown
    `min_duration` determines how many frames the mouse has to be below ground
        for the bout to be included in the video
    `blank_duration` determines who many white frames are displayed between
        time intervals where the mouse is underground
    `bouts_slice` is a slice object that determines which bouts are included in
        the video.
    `video_part` determines which part of a longer video will be produced
    """
    logging.info('Analyze video `%s`', result_file)
    
    # load the respective result file 
    analyzer = load_result_file(result_file)
    
    # determine the bouts of this video
    bouts = get_underground_bouts(analyzer, bouts_slice, video_part)
    
    if len(bouts) == 0:
        raise RuntimeError('There are no bouts that could be turned into a '
                           'video. This could be a problem with finding the '
                           'mouse trajectory in the analysis or it could '
                           'indicate that the requested bouts_slice or '
                           'video_part resulted in empty data.')

    # load the original video
    video_info = analyzer.load_video()
    frame_offset = video_info['frames'][0]
    
    # crop the video to the cage
    video_input = analyzer.video
    cropping_cage = analyzer.data['pass1/video/cropping_cage']
    if cropping_cage:
        video_input = FilterCrop(video_input, rect=cropping_cage)
    
    # determine the filename of the output video
    if output_video is None:
        # determine the complete filename automatically
        movie_ext = analyzer.params['output/video/extension']
        if video_part is None:
            filename = 'underground'
        else:
            filename = 'underground_%d' % video_part
        output_video = analyzer.get_filename(filename + movie_ext,
                                             'video_underground')
        
    elif '.' not in output_video:
        # determine the extension automatically
        movie_ext = analyzer.params['output/video/extension']
        output_video = output_video + movie_ext
    
    logging.info('Write output to `%s`', output_video)
    
    # create the video writer
    video_codec = analyzer.params['output/video/codec']
    video_bitrate = analyzer.params['output/video/bitrate']
    fps = video_input.fps
    video_output = VideoComposer(
        output_video, size=video_input.size, fps=fps, is_color=False,
        codec=video_codec, bitrate=video_bitrate,
    )
    
    # create blank frame with mean color of video
    blank_frame = np.full(video_input.shape[1:], video_input[0].mean(),
                          dtype=np.uint8)
    
    # time label position
    label_pos = video_input.width // 2, 30

    # calculate size of scale bar and the position of its label
    pixel_size_cm = analyzer.data['pass2/pixel_size_cm']
    scale_bar_size_cm = 10
    scale_bar_size_px = np.round(scale_bar_size_cm / pixel_size_cm)
    scale_bar_rect = Rectangle(30, 50, scale_bar_size_px, 5)
    scale_bar_pos = (30 + scale_bar_size_px//2, 30)
    
    # iterate over all bouts
    for start, finish in display_progress(bouts):
        
        duration = finish - start + 1 
        
        # check whether bout is long enough
        if duration < min_duration:
            continue
        
        if video_output.frames_written > 1:
            # write blank frame
            for _ in xrange(blank_duration):
                video_output.set_frame(blank_frame)
                
        video_bout = video_input[start - frame_offset : 
                                 finish - frame_offset + 1]
        
        for frame_id, frame in enumerate(video_bout, start):
            video_output.set_frame(frame, copy=True)

            if scale_bar:
                # show a scale bar
                video_output.add_rectangle(scale_bar_rect, width=-1)
                video_output.add_text(str('%g cm' % scale_bar_size_cm),
                                      scale_bar_pos, color='w',
                                      anchor='upper center')

            # gather data about this frame
            frame_data = {'frame': frame_id}
            
            # calculate time stamp
            time_secs, time_frac = divmod(frame_id, fps)
            time_msecs = int(1000 * time_frac / fps)
            dt = datetime.timedelta(seconds=time_secs,
                                    milliseconds=time_msecs)
            frame_data['time'] = str(dt)
                
            # output the display data
            if display:
                display_text = display.format(**frame_data)
                video_output.add_text(display_text, label_pos, color='w',
                                      anchor='upper center')

    # show summary
    frames_total = video_info['frames'][1] - video_info['frames'][0]
    frames_written = video_output.frames_written
    logging.info('%d (%d%%) of %d frames written', frames_written,
                 100 * frames_written // frames_total, frames_total)
    
    # close and finalize video
    try:
        video_output.close()
    except IOError:
        logging.exception('Error while writing out the debug video `%s`',
                          video_output)
        
def main():
    """ main routine of the program """
    # parse the command line arguments
    parser = argparse.ArgumentParser(description='Analyze antfarm polygons')
    parser.add_argument('-c', '--result_csv', dest='result_csv', type=str,
                        metavar='FILE.csv',
                        help='csv file to which statistics about the burrows '
                             'are written')
    parser.add_argument('-p', '--result_pkl', dest='result_pkl', type=str,
                        metavar='FILE.pkl',
                        help='python pickle file to which all results from the '
                             'algorithm are written')
    parser.add_argument('-l', '--load_pkl', dest='load_pkl', type=str,
                        metavar='FILE.pkl',
                        help='python pickle file from which data is loaded')
    parser.add_argument('-f', '--folder', dest='folder', type=str,
                        help='folder where output images will be written to')
    parser.add_argument('--scale', dest='scale', type=float,
                        default=default_parameters['scale_bar/length_cm'],
                        help='length of the scale bar in cm')
    flags = parser.add_mutually_exclusive_group(required=False)
    flags.add_argument('-m', '--multi-processing', dest='multiprocessing',
                        action='store_true', help='turns on multiprocessing')
    flags.add_argument('-d', '--debug', dest='debug', action='store_true',
                        help='does debug output')
    parser.add_argument('files', metavar='FILE', type=str, nargs='*',
                        help='files to analyze')

    # parse the command line arguments
    args = parser.parse_args()

    if args.debug:
        logging.getLogger().setLevel(logging.DEBUG)

    if args.folder:
        ensure_directory_exists(args.folder)

    if args.load_pkl:
        # load file from pickled data
        logging.info('Loading data from file `%s`.' % args.load_pkl)
        with open(args.load_pkl, "rb") as fp:
            results = pickle.load(fp)

    else:
        # get files to analyze
        files = args.files
        logging.info('Analyzing %d files.' % len(files))

        # collect burrows from all files
        if args.multiprocessing:
            # use multiple processes to analyze data
            job_func = functools.partial(process_polygon_file,
                                         output_folder=args.folder,
                                         suppress_exceptions=True,
                                         scale=args.scale)
            pool = mp.Pool()
            results = pool.map(job_func, files)

        else:
            # analyze data in the current process
            job_func = functools.partial(process_polygon_file,
                                         output_folder=args.folder,
                                         suppress_exceptions=False,
                                         debug=args.debug, scale=args.scale)
            
            # iterate over all files
            results = []
            for path in misc.display_progress(files):
                results.append(job_func(path))

        # filter results
        results = [res for res in results if res is not None]

        # write complete results as pickle file if requested
        if args.result_pkl:
            with open(args.result_pkl, "wb") as fp:
                pickle.dump(results, fp)

    # write burrow results as csv file if requested
    if args.result_csv:
        # create a dictionary of lists
        table = collections.defaultdict(list)
        # iterate through all experiments and save information about the burrows
        for data in results:
            if data:
                # sort the burrows from left to right
                burrows = sorted(data['burrows'],
                                 key=operator.itemgetter('pos_x'))
                # create a single row per burrow
                for burrow_id, properties in enumerate(burrows, 1):
                    properties['burrow_id'] = burrow_id
                    properties['experiment'] = data['name']
                    # iterate over all burrow properties
                    for k, v in properties.iteritems():
                        table[k].append(v)

        # write the data to a csv file
        first_columns = ['experiment', 'burrow_id']
        data_structures.misc.save_dict_to_csv(table, args.result_csv,
                                              first_columns=first_columns)