def run_timepoint(self, scheduled_start):
    try:
        self.heartbeat()
        self.timepoint_prefix = time.strftime('%Y-%m-%dt%H%M')
        self.scheduled_start = scheduled_start
        self.start_time = time.time()
        self._job_futures = []
        self.logger.info('Starting timepoint {} ({:.0f} minutes after scheduled)', self.timepoint_prefix, ((self.start_time - self.scheduled_start) / 60))
        self.experiment_metadata.setdefault('timepoints', []).append(self.timepoint_prefix)
        self.experiment_metadata.setdefault('timestamps', []).append(self.start_time)
        self.logger.info('Configuring timepoint')
        with self.debug_timing('Configuration'):
            self.configure_timepoint()
        self.heartbeat()
        self.run_all_positions()
        self.finalize_timepoint()
        self.heartbeat()
        self.end_time = time.time()
        self.experiment_metadata.setdefault('durations', []).append((self.end_time - self.start_time))
        if self.write_files:
            self._write_atomic_json(self.experiment_metadata_path, self.experiment_metadata)
        run_again = (self.skip_positions != self.positions.keys())
        with self.debug_timing('Image IO'), self.heartbeat_timer():
            self.image_io.wait()
        if self._job_futures:
            with self.debug_timing('Background jobs'), self.heartbeat_timer():
                futures.wait(self._job_futures)
            [f.result() for f in self._job_futures]
        self.cleanup()
        self.logger.info('Timepoint {} ended ({:.0f} minutes after starting)', self.timepoint_prefix, ((time.time() - self.start_time) / 60))
        if run_again:
            return self.get_next_run_time()
    except:
        self.logger.error('Exception in timepoint:', exc_info=True)
        raise
def run_timepoint(self, scheduled_start):
    try:
        self.heartbeat()
        self.timepoint_prefix = time.strftime('%Y-%m-%dt%H%M')
        self.scheduled_start = scheduled_start
        self.start_time = time.time()
        self._job_futures = []
        self.logger.info('Starting timepoint {} ({:.0f} minutes after scheduled)', self.timepoint_prefix, ((self.start_time - self.scheduled_start) / 60))
        self.experiment_metadata.setdefault('timepoints', []).append(self.timepoint_prefix)
        self.experiment_metadata.setdefault('timestamps', []).append(self.start_time)
        self.logger.info('Configuring timepoint')
        with self.debug_timing('Configuration'):
            self.configure_timepoint()
        self.heartbeat()
        self.run_all_positions()
        self.finalize_timepoint()
        self.heartbeat()
        self.end_time = time.time()
        self.experiment_metadata.setdefault('durations', []).append((self.end_time - self.start_time))
        if self.write_files:
            self._write_atomic_json(self.experiment_metadata_path, self.experiment_metadata)
        run_again = (self.skip_positions != self.positions.keys())
        with self.debug_timing('Image IO'), self.heartbeat_timer():
            self.image_io.wait()
        if self._job_futures:
            with self.debug_timing('Background jobs'), self.heartbeat_timer():
                futures.wait(self._job_futures)
            [f.result() for f in self._job_futures]
        self.cleanup()
        self.logger.info('Timepoint {} ended ({:.0f} minutes after starting)', self.timepoint_prefix, ((time.time() - self.start_time) / 60))
        if run_again:
            return self.get_next_run_time()
    except:
        self.logger.error('Exception in timepoint:', exc_info=True)
        raise