def get_delayed_start_interval(self, parent, notifyinterval=None): self._tiw = TimerIntervalWindow(parent, notifyinterval) parent.wait_window(self._tiw._window) self._tiw = None
class ProtocolOperations(): def __init__(self, canvas, shootoff): self._canvas = canvas self._plugin_canvas_artifacts = [] self._shootoff = shootoff self._feed_text = self._canvas.create_text(1, 1, anchor="nw", fill="white") self._plugin_canvas_artifacts.append(self._feed_text) self._added_columns = () self._added_column_widths = [] self._sound_cache = {} self._tiw = None self._destroy = False self._tts_engine = pyttsx.init() # slow down the wpm rate otherwise they speek to fast self._tts_engine.setProperty("rate", 150) self._tts_engine.startLoop(False) # Shows a popup window that lets the user set the interval for a random start # delay in seconds. Notify interval points to a function that gets the min # and max values for the interval as parameters. def get_delayed_start_interval(self, parent, notifyinterval=None): self._tiw = TimerIntervalWindow(parent, notifyinterval) parent.wait_window(self._tiw._window) self._tiw = None # Returns the centroid of a target using the specified mode: # LARGEST_REGION calculates the centroid of the target by calculating # the centroid of the largest region. The largest region is determined # by calculating the area of the each region's bounding box (this isn't as # accurate as determining the area of each region, but it's simple). This # mode works well for targets with stacked regions (e.g. a traditional bullseye). # BOUNDING_BOX calculates the centroid of a target by calculating the center of # the bounding box that encompasses all of a target's region. This mode works # well for targets whose regions are not stacked (e.g. a target with 5 separate # bullseyes). def calculate_target_centroid(self, target, mode=LARGEST_REGION): coords = () target_name = "_internal_name" + ":" + target["regions"][0]["_internal_name"] if mode == LARGEST_REGION: regions = self._canvas.find_withtag(target_name) largest_region = None # Find the largest region by bounding box size for region in regions: if largest_region is None: largest_region = region elif self._area_bbox(largest_region) < self._area_bbox(region): largest_region = region coords = self._canvas.coords(largest_region) elif mode == BOUNDING_BOX: coords = self._canvas.bbox(target_name) # Calculate centroid x = coords[::2] y = coords[1::2] return (sum(x) / len(x), sum(y) / len(x)) # This method expects to get the id of a target region on a canvas and will return # the area of its bounding box def _area_bbox(self, region): coords = self._canvas.bbox(region) width = coords[2] - coords[0] height = coords[3] - coords[1] return (width * height) # new_columns is a tuple containing the names of the new columns to added # widths is a list of each column's width in pixels. # it must be true that len(new_columns) == len(column_sizes) def add_shot_list_columns(self, new_columns, widths): self._added_columns += new_columns if len(self._added_column_widths) == 0: self._added_column_widths = widths else: self._added_column_widths += widths self._shootoff.add_shot_list_columns(new_columns) self._shootoff.configure_default_shot_list_columns() self._shootoff.configure_shot_list_columns(self._added_columns, self._added_column_widths) # appends the tuple values the value tuple that already exists for item. # This is how data is added by a training protocol to columns it added. def append_shot_item_values(self, item, values): self._shootoff.append_shot_list_column_data(item, values) def destroy(self): self._destroy = True if (self._tiw is not None): self._tiw.destroy() # pyttsx errors out if we try to end a loop that isn't running, so # we need to check if we are in a loop first, but the only good # way to do this right now is to check an internal flag. This hack # checks that the flag exists and checks it before ending the loop # if it does, otherwise we just end the loop (better to get a CLI # error message than the actual behavior of not ending the loop, # which is weird sound artifacts). if hasattr(self._tts_engine, "_inLoop") and self._tts_engine._inLoop: self._tts_engine.endLoop() elif not hasattr(self._tts_engine, "_inLoop"): self._tts_engine.endLoop() self.clear_canvas() self.clear_protocol_shot_list_columns() self.pause_shot_detection(False) # If pause is set to true hit's won't register. If pause is set to false # hits will register. def pause_shot_detection(self, pause): self._shootoff.pause_shot_detection(pause) # This clears shots without resetting the current protocol (it is # identical to clicking the clear shots button aside from the protocol's # reset method being called) def clear_shots(self): self._shootoff.clear_shots() # This performs the same operation as hitting the reset button def reset(self): self._shootoff.reset_click() # Use text-to-speech to say message outloud def say(self, message): # if we don't do this on another thread we have to wait until # the message has finished being communicated to do anything # (i.e. shootoff freezes) self._say_thread = Thread(target=self._say, args=(message,), name="say_thread") self._say_thread.start() def _say(self, *args): if self._destroy: return self._tts_engine.say(args[0]) if hasattr(self._tts_engine, "_inLoop") and self._tts_engine._inLoop: self._tts_engine.iterate() # Show message as text on the top left corner of the webcam feed. The # new message will over-write whatever was shown before def show_text_on_feed(self, message): self._canvas.itemconfig(self._feed_text, text=message) # Remove anything added by the plugin from the canvas def clear_canvas(self): for artifact in self._plugin_canvas_artifacts: self._canvas.delete(artifact) # Removes all traces of shot list columns/data added by the plugin def clear_protocol_shot_list_columns(self): self._shootoff.revert_shot_list_columns() def _cache_sound(self): wavs = glob.glob("sounds/*.wav") for wav in wavs: self._add_wav_cache(wav) # Returns true of the projector arena is open, false otherwise def projector_arena_visible(self): return self._shootoff.get_projector_arena().is_visible() # Adds a target to the projector arena where name is the name of the .target file to use # (e.g. targets/ISSF.target) and x, y is the location of the top left corner # of the target def add_projector_target(self, name, x, y): arena = self._shootoff.get_projector_arena() target_name = arena.add_target_loc(name, x, y); return target_name def delete_projector_target(self, target_name): arena = self._shootoff.get_projector_arena() arena.delete_target(target_name); def get_target_name(self, region): arena = self._shootoff.get_projector_arena() for tag in arena.get_canvas().gettags(region): if tag.startswith("_internal_name:"): return tag # Returns a (width, height) tuple for the projector arena's canvas def get_projector_arena_dimensions(self): arena = self._shootoff.get_projector_arena() return (arena.arena_width(), arena.arena_height()) # Play the sound in sound_files def play_sound(self, sound_file): # if we don't do this on a nother thread we have to wait until # the message has finished being communicated to do anything # (i.e. shootoff freezes) self._play_sound_thread = Thread(target=self._play_sound, args=(sound_file,), name="play_sound_thread") self._play_sound_thread.start() def _play_sound(self, *args): if self._destroy: return sound_file = args[0] if sound_file not in self._sound_cache: self._add_wav_cache(sound_file) # initialize the sound file and stream p = pyaudio.PyAudio() stream = p.open(format = p.get_format_from_width(self._sound_cache[sound_file][SAMPWIDTH_INDEX]), channels = self._sound_cache[sound_file][NCHANNELS_INDEX], rate = self._sound_cache[sound_file][FRAMERATE_INDEX], output = True) # play the sound file for data in self._sound_cache[sound_file][DATA_INDEX]: if self._destroy: break stream.write(data) # clean up stream.stop_stream() stream.close() p.terminate() def _add_wav_cache(self, sound_file): chunk = 1024 f = wave.open(sound_file,"rb") self._sound_cache[sound_file] = (f.getsampwidth(), f.getnchannels(), f.getframerate(), []) data = f.readframes(chunk) while data != '': self._sound_cache[sound_file][DATA_INDEX].append(data) data = f.readframes(chunk)