Exemplo n.º 1
0
class MJPEGStreamer(object):
    _httpPort = 8085

    def __init__(self, videoDevice, size, fps, format):
        self._logger = logging.getLogger(__name__)
        self._device = '/dev/video%d' % videoDevice
        self._size = size
        self._fps = fps
        self._format = format
        self._videoRunning = False
        self._process = None
        self._streaming = False
        self._needsExposure = True

        self._infoArea = cv2.imread(
            os.path.join(app.static_folder, 'img', 'camera-info-overlay.jpg'),
            cv2.cv.CV_LOAD_IMAGE_COLOR)
        self._infoAreaShape = self._infoArea.shape

        #precalculated stuff
        watermark = cv2.imread(
            os.path.join(app.static_folder, 'img', 'astroprint_logo.png'))
        watermark = cv2.resize(
            watermark, (100, 100 * watermark.shape[0] / watermark.shape[1]))

        self._watermarkShape = watermark.shape

        watermarkMask = cv2.cvtColor(watermark, cv2.COLOR_BGR2GRAY) / 255.0
        watermarkMask = np.repeat(watermarkMask, 3).reshape(
            (self._watermarkShape[0], self._watermarkShape[1], 3))
        self._watermakMaskWeighted = watermarkMask * watermark
        self._watermarkInverted = 1.0 - watermarkMask

    def startStreamer(self):
        if not self._process:
            command = [
                "/mjpeg_streamer/mjpg_streamer", "-i",
                "input_uvc.so -d %s -f %s -r %s --no_dynctrl%s" %
                (self._device, self._fps, self._size,
                 ' -y' if self._format == 'x-raw' else ''), "-o",
                "output_http.so -p %d" % self._httpPort
            ]

            self._process = Command(command,
                                    env={'LD_LIBRARY_PATH': '/mjpeg_streamer'},
                                    stderr=open(os.devnull, 'w'))
            if self._process:
                self._process.run(async=True)

                time.sleep(0.2)

                running = self._process.returncode is None

                return running

        return False

    def startVideo(self):
        if self._streaming:
            return True

        if self.startStreamer():
            self._streaming = True
            return True

        return False

    def stop(self):
        if self._process:
            if self._process.returncode is None:
                self._process.terminate()
                tries = 4
                while self._process.returncode is None:
                    if tries > 0:
                        tries -= 1
                        time.sleep(0.5)
                        self._process.poll()
                    else:
                        break

                if self._process.returncode is None:
                    self._logger.warn(
                        'Unable to terminate nicely, killing the process.')
                    self._process.kill()
                    self._process.wait()

            self._process = None

        self._streaming = False
        self._needsExposure = True

    def stopVideo(self):
        self._streaming = False

    def isVideoStreaming(self):
        return self._streaming

    def getPhoto(self, doneCb, text=None):
        image = None

        if not self._process:
            if not self.startStreamer():
                self._logger.error('Unable to start MJPEG Streamer')
                doneCb(None)
                return

        try:
            if self._needsExposure and not self.isVideoStreaming():
                time.sleep(
                    1.8
                )  # we need to give the camera some time to stabilize the image. 1.8 secs has been tested to work in low end cameras
                self._needsExposure = False

            response = urllib2.urlopen('http://127.0.0.1:%d?action=snapshot' %
                                       self._httpPort)
            image = response.read()

        except urllib2.URLError as e:
            self._logger.error(e)

        if image and text:
            decodedImage = cv2.imdecode(np.fromstring(image, np.uint8),
                                        cv2.CV_LOAD_IMAGE_COLOR)
            self._apply_watermark(decodedImage, text)
            image = cv2.cv.EncodeImage(
                '.jpeg', cv2.cv.fromarray(decodedImage),
                [cv2.cv.CV_IMWRITE_JPEG_QUALITY, 80]).tostring()

        doneCb(image)

    def _apply_watermark(self, img, text):
        if text and img != None:
            imgPortion = img[-(self._watermarkShape[0] + 5):-5,
                             -(self._watermarkShape[1] + 5):-5]
            img[-(self._watermarkShape[0] + 5):-5,
                -(self._watermarkShape[1] +
                  5):-5] = (self._watermarkInverted *
                            imgPortion) + self._watermakMaskWeighted

            img[:self._infoAreaShape[0], :self.
                _infoAreaShape[1]] = self._infoArea
            cv2.putText(img,
                        text, (30, 17),
                        cv2.FONT_HERSHEY_PLAIN,
                        1.0, (81, 82, 241),
                        thickness=1)

            return True

        return False
Exemplo n.º 2
0
class IsotropySession:
    """
    Make simple requests to isotropy.
    isotropy session is kept running in background until closed
    should be used with 'with' statements to ensure isotropy is exited properly
    ex:
    with IsotropySession() as isos:
        do things with isos
    """
    def __init__(self, values=None, shows=None, labels=None, setting=None):
        """
        Args:
        values: dictionary of keys to be set to values
            key is a string specifying what is being set
                e.g. "basis", "cell", "irrep", "kpoint", "parent"
            value is what this key is being set to
        shows: A list of strings corresponding to data which will be returned
            when display is run.
            Note: some show commands accept additional parameters, for now
            these must be included in the string. Eventually parsing of
            display output should be good enough that they are not needed.
        labels: NOT YET IMPLEMENTED
            dictionary where the key corresponds to the object
            whose notation is being altered, the value corresponds
            to the new notation to be used for this object
            for example {"spacegroup": "SCHOENFLIES"} will cause returned
            results and entered values to use schoenflies notation
        setting:
            a string or list of strings to be passed to
            the setting command can be used to change settings, origin,
            unique axis and/or cell choice. Can also specify if magnetic
            spacegroups are desired
            for now the setting options can only be set
            when creating an Isotropy object, not changed later
        """
        iso_location = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                    'iso/')
        logger.debug("""starting isotropy session in {}
                        using isotropy in: {}""".format(
            os.getcwd(), iso_location))
        self.iso_process = Command(os.path.join(iso_location, 'iso'),
                                   stdout=Capture(buffer_size=1),
                                   env={"ISODATA": iso_location})
        try:
            self.iso_process.run(input=PIPE, async_=True)
        except FileNotFoundError:
            raise Exception(
                "Couldn't find Isotropy for Linux, see installation instructions"
            )
        # move past initial output
        keep_reading = True
        while keep_reading:
            # this_line = self.iso_process.stdout.readline().decode()
            this_line = self.read_iso_line()
            if this_line:  # don't log until isotropy responds
                logger.debug("isotropy: {}".format(this_line))
            if this_line == 'Use "VALUE IRREP VERSION" to change version':
                keep_reading = False

        self.screen = 999  # exploit this too make parsing output easier?
        self.sendCommand("SCREEN {}".format(self.screen))
        #self.page = "NOBREAK" # still feels the need to periodicly put in labels
        self.page = "999"
        self.sendCommand("PAGE {}".format(self.page))
        if setting:
            if type(setting) == list:
                self.setting = setting
            else:
                self.setting = [setting]
        else:
            self.setting = ["INTERNATIONAL"]
        for s in self.setting:
            self.sendCommand("SETTING {}".format(s))
        self.values = Values(self, values)
        self.shows = Shows(self, shows)

    def __enter__(self):
        return self

    def __exit__(self, exec_type, exc_value, exc_traceback):
        self.sendCommand("QUIT")

    def restart_session(self):
        try:
            self.sendCommand("QUIT")
        except BrokenPipeError:
            logger.debug('Ignoring BrokenPipeError on restart')
        self.iso_process.kill()
        files_to_remove = glob("*.iso")
        logger.warning("removing iso db files {}".format(files_to_remove))
        for f in files_to_remove:
            os.remove(f)
        self.__init__(
            values=self.values,
            shows=self.shows,
            labels=None,
            setting=self.setting)  # TODO: update if labels implemented

    def sendCommand(self, command):
        # read the '*' that indicates the prompt so they don't build up
        this_line = self.read_iso_line()
        # logger.debug("reading *: {}".format(this_line))
        logger.debug(f'python: {command}')
        self.iso_process.stdin.write(bytes(command + "\n", "ascii"))
        self.iso_process.stdin.flush()

    def getDisplayData(self, display, raw=False, delay=None):
        """
        Args:
        display: what we are asking isotropy to display
            the command sent will be 'DISPLAY {display}'
        raw: if true return a string of the raw output from isotropy
            otherwise the output is automaticly parsed in to a list of dictionaries
        """
        self.sendCommand("DISPLAY {}".format(display))
        # really annoying, TODO: should find a way to not need this delay ever
        if delay is not None:
            time.sleep(delay)
        lines = []
        keep_reading = True
        while keep_reading:
            this_line = self.read_iso_line()
            if this_line in ['*',
                             '']:  # if there is no output '' is returned above
                keep_reading = False
            elif re.match(".*You have requested information about .*",
                          this_line):
                self.read_iso_line()  # read past irrep:...
                self.read_iso_line()  # read past The data base for these...
                self.read_iso_line()  # read past Should this...
                self.read_iso_line()  # read past Enter RETURN
                self.sendCommand("")
                self.read_iso_line()  # read past Adding
                for i in range(10):
                    possibly_blank = self.read_iso_line()
                    if not (possibly_blank in [
                            '*', ''
                    ]):  # if there is no output '' is returned above
                        logger.debug(
                            "moved past data base prompt, adding results")
                        lines.append(possibly_blank)
                        break
                    else:
                        if i == 9:
                            logger.debug(
                                "moved past data base prompt, no results")
            elif re.match(".*Data base for these coupled subgroups .*",
                          this_line):
                self.read_iso_line()  # read past Should this...
                self.read_iso_line()  # read past Enter RETURN
                self.sendCommand("")
                self.read_iso_line()  # read past Adding
                for i in range(10):
                    possibly_blank = self.read_iso_line()
                    if not (possibly_blank in [
                            '*', ''
                    ]):  # if there is no output '' is returned above
                        logger.debug(
                            "moved past data base prompt, adding results")
                        lines.append(possibly_blank)
                        break
                    else:
                        if i == 9:
                            logger.debug(
                                "moved past data base prompt, no results")
            else:
                lines.append(this_line)
        if not raw:
            return self._parse_output(lines)
        return lines

    def read_iso_line(self):
        raw = self.iso_process.stdout.readline().decode()
        this_line = raw.rstrip('\n')
        logger.debug("isotropy: {}".format(this_line))
        if re.match('.*program\shas\sbombed.*', this_line):
            raise IsotropyBombedException()
        if re.match(".*Basis\svectors\sare\snot\sa\sright\-handed\sset.*",
                    this_line):
            raise IsotropyBasisException()
        if re.match(
                ".*not\sall\selements\sof\sthe\ssubgroup\sare\selements\sof\sparent\sgroup.*",
                this_line):
            raise IsotropySubgroupException()
        return this_line

    def _parse_output(self, lines):
        indexes = detect_column_indexes(lines)
        split_by_ind = [split_line_by_indexes(indexes, line) for line in lines]
        parsed_output = [{
            key: detect_data_form_and_convert(prop)
            for key, prop in result.items()
        } for result in detect_multirows_and_split(split_by_ind)]
        return parsed_output
Exemplo n.º 3
0
class MJPEGStreamer(object):
	_httpPort = 8085

	def __init__(self, videoDevice, size, fps, format):
		self._logger = logging.getLogger(__name__)
		self._device = '/dev/video%d' % videoDevice
		self._size = size
		self._fps = fps
		self._format = format
		self._videoRunning = False
		self._process = None
		self._streaming = False
		self._needsExposure = True

		self._infoArea = cv2.imread(os.path.join(app.static_folder, 'img', 'camera-info-overlay.jpg'), cv2.cv.CV_LOAD_IMAGE_COLOR)
		self._infoAreaShape = self._infoArea.shape

		#precalculated stuff
		watermark = cv2.imread(os.path.join(app.static_folder, 'img', 'astroprint_logo.png'))
		watermark = cv2.resize( watermark, ( 100, 100 * watermark.shape[0]/watermark.shape[1] ) )

		self._watermarkShape = watermark.shape

		watermarkMask = cv2.cvtColor(watermark, cv2.COLOR_BGR2GRAY) / 255.0
		watermarkMask = np.repeat( watermarkMask, 3).reshape( (self._watermarkShape[0],self._watermarkShape[1],3) )
		self._watermakMaskWeighted = watermarkMask * watermark
		self._watermarkInverted = 1.0 - watermarkMask

	def startStreamer(self):
		if not self._process:
			command = [
				"/mjpeg_streamer/mjpg_streamer",
				"-i",
				"input_uvc.so -d %s -f %s -r %s --no_dynctrl%s" % (self._device, self._fps, self._size, ' -y' if self._format == 'x-raw' else ''),
				"-o",
				"output_http.so -p %d" % self._httpPort
			]

			self._process = Command(command, env={'LD_LIBRARY_PATH': '/mjpeg_streamer'}, stderr=open(os.devnull, 'w'))
			if self._process:
				self._process.run(async=True)

				time.sleep(0.2)

				running = self._process.returncode is None

				return running

		return False

	def startVideo(self):
		if self._streaming:
			return True

		if self.startStreamer():
			self._streaming = True
			return True

		return False

	def stop(self):
		if self._process:
			if self._process.returncode is None:
				self._process.terminate()
				tries = 4
				while self._process.returncode is None:
					if tries > 0:
						tries -= 1
						time.sleep(0.5)
						self._process.poll()
					else:
						break

				if self._process.returncode is None:
					self._logger.warn('Unable to terminate nicely, killing the process.')
					self._process.kill()
					self._process.wait()

			self._process = None

		self._streaming = False
		self._needsExposure = True

	def stopVideo(self):
		self._streaming = False

	def isVideoStreaming(self):
		return self._streaming

	def getPhoto(self, doneCb, text=None):
		image = None

		if not self._process:
			if not self.startStreamer():
				self._logger.error('Unable to start MJPEG Streamer')
				doneCb(None)
				return

		try:
			if self._needsExposure and not self.isVideoStreaming():
				time.sleep(1.8) # we need to give the camera some time to stabilize the image. 1.8 secs has been tested to work in low end cameras
				self._needsExposure = False

			response = urllib2.urlopen('http://127.0.0.1:%d?action=snapshot' % self._httpPort)
			image = response.read()

		except urllib2.URLError as e:
			self._logger.error(e)

		if image and text:
			decodedImage = cv2.imdecode(np.fromstring(image, np.uint8), cv2.CV_LOAD_IMAGE_COLOR)
			self._apply_watermark(decodedImage, text)
			image = cv2.cv.EncodeImage('.jpeg', cv2.cv.fromarray(decodedImage), [cv2.cv.CV_IMWRITE_JPEG_QUALITY, 80]).tostring()

		doneCb(image)

	def _apply_watermark(self, img, text):
		if text and img != None:
			imgPortion = img[-(self._watermarkShape[0]+5):-5, -(self._watermarkShape[1]+5):-5]
			img[-(self._watermarkShape[0]+5):-5, -(self._watermarkShape[1]+5):-5] = (self._watermarkInverted * imgPortion) + self._watermakMaskWeighted

			img[:self._infoAreaShape[0], :self._infoAreaShape[1]] = self._infoArea
			cv2.putText(img, text, (30,17), cv2.FONT_HERSHEY_PLAIN, 1.0, (81,82,241), thickness=1)

			return True

		return False
Exemplo n.º 4
0
class Shell(AbstractContextManager):
    """A real shell running on the host machine, to be used in a context."""
    def __init__(self, shell_binary, shell_argstr, return_code_echo_command,
                 command_separator, is_interactive):
        """Sets up the context for a system shell process.

        Parameters
        ----------
        shell_binary : str
            The shell binary, e.g. ``/bin/bash``, to start.
        shell_argstr : str
            Additional arguments to be passed to the shell at start.
        return_code_echo_command : str
            A command string in the shell's own language that prints to
            standard output the return code of the previously executed command.
        command_separator : str
            The character sequence to separate commands with.
        is_interactive : bool
            Whether the started shell is an interactive one.
            This does only change the behaviour of the context manager, to make
            the shell itself interactive, additional arguments in
            `shell_argstr` might need to be passed.
        """
        self._args = shell_argstr
        self._binary = shell_binary
        # Note: The execution of the command and the reading of the output
        # has to happen BEFORE this timeout is hit, but a large timeout would
        # also mean waiting a lot for small commands, so this has to be
        # balanced carefully.
        self._capture = Capture(timeout=0.5, buffer_size=-1)
        self._command = Command(shell_binary + ' ' + shell_argstr,
                                stdout=self._capture)
        self._echo = return_code_echo_command + command_separator
        self._interactive = is_interactive
        self._separator = command_separator
        self._started = False

    def __enter__(self):
        """Starts the shell in a context manager setting."""
        return self.start()

    def __exit__(self, exc_type, exc_value, trace):
        """Destroys the shell and leaves the context."""
        if self._interactive:
            self.kill()
        else:
            self.terminate()
        return False

    def start(self):
        """Starts the underlying shell process as configured in
        :py:func:`__init__`.
        """
        if self._started:
            raise OSError(EEXIST, "The shell is already running!")
        print("[Shell] Starting '{0} {1}'...".format(self._binary, self._args),
              file=sys.stderr)
        try:
            self._command.run(input=PIPE, async_=True)
        except ValueError:
            raise FileNotFoundError("The shell binary '{0}' cannot be found "
                                    "on the system.".format(self._binary))
        self._started = True
        return self

    @property
    def pid(self):
        """The PID of the started process."""
        if not self._started:
            raise OSError(ESRCH, "The shell is not running!")
        return self._command.process.pid

    def kill(self):
        """Kills (by sending ``SIGKILL`` (``9``)) to the shell process."""
        if not self._started:
            raise OSError(ESRCH, "The shell is not running!")
        self._command.kill()
        self._command.wait()
        self._capture.close(True)
        self._started = False

    def terminate(self):
        """Terminates (by sending ``SIGTERM`` (``15``)) to the shell process.

        Note
        ----
        Interactive shells (:py:attr:`is_interactive`) usually catch and ignore
        this signal, and as such, :py:func:`kill` should be used to shut them
        down properly.
        """
        if not self._started:
            raise OSError(ESRCH, "The shell is not running!")
        self._command.terminate()
        self._command.wait()
        self._capture.close(True)
        self._started = False

    def execute_command(self, cmd, timeout=None):
        """Execute `cmd` in the shell, wait `timeout` seconds, and read back
        the result.

        Parameters
        ----------
        cmd : str
            The command to execute.
            This string will be written into the shell's standard input
            verbatim.
        timeout : int
            The time (in seconds) to wait before the output of the command is
            read.

        Returns
        -------
        return_code : int
            The return code of the executed command.
        result : str
            The *standard output* of the executed command.

        Note
        ----
        The command executed in the shell is extended with
        :py:attr:`command_separator` and :py:attr:`return_code_echo_command`,
        and written to the shell.
        In case of a conventional ``/bin/bash`` shell, for example, executing
        `cmd` ``echo "Foo"`` will actually execute:

        .. code-block:: bash

           echo "Foo";
           echo $;

        Which will result in the output:

        .. code-block:: bash

           Foo
           0

        to be read as a result.

        Warning
        -------
        The underlying library and the execution of piped shells does not allow
        a good method of "sensing" when the output became available while
        keeping interactivity.
        A too small `timeout` on a loaded system might result in output being
        lost, while a too big one will result in every command waiting for
        a considerable time.
        """
        cmd = cmd + self._separator + self._echo
        print("[Shell '{0}'] Running command:\n{1}\n".format(
            self._binary,
            '\n'.join(list(map(lambda l: "    > " + l, cmd.split('\n'))))),
              file=sys.stderr)

        self._command.stdin.write(cmd.encode('utf-8'))
        self._command.stdin.flush()

        stdout = self._capture.read(timeout=timeout)
        parts = stdout.decode().rstrip().split('\n')
        result, returncode = '\n'.join(parts[:-1]).rstrip(), parts[-1].rstrip()

        print("[Shell '{0}'] Command result #{1}:\n{2}".format(
            self._binary, returncode, result),
              file=sys.stderr)
        return int(returncode), result