Beispiel #1
0
def render(
    xs: np.array,
    ys: np.array,
    x_min: float,
    x_max: float,
    y_min: float,
    y_max: float,
    width: int,
    height: int,
) -> np.array:
    """
    Turn a list of 2D points into a raster matrix.

    Returns the pixels as 2D array with 1 or 0 integer entries.

    Note that the row order is optimized for drawing later, so the first row corresponds
    to the highest line of pixels.
    """
    assert xs.shape == ys.shape
    assert x_max > x_min
    assert y_max > y_min
    assert width > 0
    assert height > 0

    x_indices = discretize(np.array(xs), x_min, x_max, steps=width)
    y_indices = discretize(np.array(ys), y_min, y_max, steps=height)

    # Invert y direction to optimize for plotting later
    y_indices = (height - 1) - y_indices

    # Filter out of view pixels
    xy_indices = np.stack((x_indices, y_indices)).T
    xy_indices = xy_indices[(xy_indices[:, 0] >= 0)
                            & (xy_indices[:, 0] < width)
                            & (xy_indices[:, 1] >= 0)
                            & (xy_indices[:, 1] < height)]
    xy_indices = xy_indices.T

    # Assemble pixel matrix
    pixels = np.zeros((height, width), dtype=int)
    pixels[xy_indices[1], xy_indices[0]] = 1

    return pixels
Beispiel #2
0
def render_vertical_gridline(x: float, options: Options) -> np.array:
    """
    Render the pixel matrix that only consists of a line where the `x` value is.
    """
    pixels = _init_character_matrix(width=options.width, height=options.height)
    if x < options.x_min or x >= options.x_max:
        return pixels

    x_index = discretize(
        x=x, x_min=options.x_min, x_max=options.x_max, steps=options.width
    )

    pixels[:, x_index] = "│"

    return pixels
Beispiel #3
0
def render_horizontal_gridline(y: float, options: Options) -> np.array:
    """
    Render the pixel matrix that only consists of a line where the `y` value is.

    Because a character is higher than wide, this is rendered with "super-resolution"
    Unicode characters.
    """
    pixels = _init_character_matrix(width=options.width, height=options.height)
    if y < options.y_min or y >= options.y_max:
        return pixels

    y_index_superresolution = (3 * options.height - 1 -
                               discretize(x=y,
                                          x_min=options.y_min,
                                          x_max=options.y_max,
                                          steps=3 * options.height))
    y_index = int(y_index_superresolution / 3)

    character = Y_GRIDLINE_CHARACTERS[y_index_superresolution % 3]
    pixels[y_index, :] = character

    return pixels
Beispiel #4
0
def test_correct_discretization_for_number():
    integer = discretize(x=2.1, x_min=1, x_max=3, steps=2)
    assert integer == 1
Beispiel #5
0
def test_correct_discretization_for_array():
    vector_float = np.array([0.01, 0.99, 1.01, 1.5, 1.99, 9.99])
    vector_integer = discretize(x=vector_float, x_min=0, x_max=10, steps=10)
    assert (vector_integer == np.array([0, 0, 1, 1, 1, 9])).all()
Beispiel #6
0
    def _render_and_measure_to_cache(self) -> None:
        # Break if result is already in cache
        if self._results_already_in_cache:
            return

        str_labels = self._find_shortest_string_representation(self.labels)

        if self.vertical_direction:
            # So this is for the y axis case
            lines: List[str] = [""] * self.available_space

            for i, label in enumerate(self.labels):
                str_label = str_labels[i]
                index = (
                    self.available_space
                    - 1
                    - min(
                        max(
                            0,
                            discretize(
                                label,
                                x_min=self.x_min,
                                x_max=self.x_max,
                                steps=self.available_space,
                            ),
                        ),
                        self.available_space - 1,
                    )
                )
                if lines[index] != "":
                    # This is bad and leads to wrong offsets
                    self._render_does_overlap = True

                lines[index] = str_label + self.unit

            self._rendered_result = lines
        else:
            # So this is for the x axis case
            line = ""
            for i, label in enumerate(self.labels):
                str_label = str_labels[i]
                offset = max(
                    0,
                    discretize(
                        label,
                        x_min=self.x_min,
                        x_max=self.x_max,
                        steps=self.available_space,
                    )
                    - int(0.5 * (len(str_label) + len(self.unit)))
                    + LEFT_MARGIN_FOR_HORIZONTAL_AXIS,
                )
                buffer = offset - len(line)
                if i == 0 and buffer < 0:
                    # This is bad and leads to wrong offsets
                    buffer = 0
                    self._render_does_overlap = True
                elif i > 0 and buffer < 1:
                    # This is bad and leads to wrong offsets
                    buffer = 1
                    self._render_does_overlap = True

                # Compose string for this line
                line = line + (" " * buffer) + str_label + self.unit

            self._rendered_result = [line]
        self._results_already_in_cache = True
Beispiel #7
0
def render(
    xs: np.array,
    ys: np.array,
    x_min: float,
    x_max: float,
    y_min: float,
    y_max: float,
    width: int,
    height: int,
    lines: bool = False,
) -> np.array:
    """
    Turn a list of 2D points into a raster matrix.

    Returns the pixels as 2D array with 1 or 0 integer entries.

    Note that the row order is optimized for drawing later, so the first row corresponds
    to the highest line of pixels.
    """
    xs = np.array(xs)
    ys = np.array(ys)

    assert xs.shape == ys.shape
    assert x_max > x_min
    assert y_max > y_min
    assert width > 0
    assert height > 0

    pixels = np.zeros((height, width), dtype=int)

    x_indices = discretize(xs, x_min, x_max, steps=width)
    y_indices = discretize(ys, y_min, y_max, steps=height)

    # Invert y direction to optimize for plotting later
    y_indices = (height - 1) - y_indices

    # Combine lists to get coordinates
    xy_indices = np.column_stack((x_indices, y_indices))

    if lines:
        xys = np.column_stack((xs, ys))

        # Compute all line segments as an array with entries of the shape:
        # [
        #     [x_index_start, y_index_start],
        #     [x_start, y_start],
        #     [x_index_stop, y_index_stop],
        #     [x_stop, y_stop]
        # ]
        xy_line_endpoints = np.stack(
            (xy_indices[:-1], xys[:-1], xy_indices[1:], xys[1:]),
            axis=1).astype(float)

        # Filter out of view line segments
        xy_line_endpoints = xy_line_endpoints[
            (
                # At least one of the x coordinates of start and end need to be >= x_min
                (xy_line_endpoints[:, 1, 0] >= x_min)
                | (xy_line_endpoints[:, 3, 0] >= x_min))
            & (
                # At least one of the x coordinates of start and end need to be < x_max
                (xy_line_endpoints[:, 1, 0] < x_max)
                | (xy_line_endpoints[:, 3, 0] < x_max))
            & (
                # At least one of the y coordinates of start and end need to be >= y_min
                (xy_line_endpoints[:, 1, 1] >= y_min)
                | (xy_line_endpoints[:, 3, 1] >= y_min))
            & (
                # At least one of the y coordinates of start and end need to be < x_max
                (xy_line_endpoints[:, 1, 1] < y_max)
                | (xy_line_endpoints[:, 3, 1] < y_max))
            & (
                # The start and stop indices need to be different by at least 2 in any
                # direction
                (np.abs(xy_line_endpoints[:, 0, 0] -
                        xy_line_endpoints[:, 2, 0]) > 1.5)
                | (np.abs(xy_line_endpoints[:, 0, 1] -
                          xy_line_endpoints[:, 2, 1]) > 1.5))]

        # TODO This can likely be optimized by assembling all segments and computing the
        # pixels of all lines together, or at least of each half split by slope
        # for segment in np.nditer(xy_line_endpoints): # TODO use this
        for segment in xy_line_endpoints:
            [
                [x_index_start, y_index_start],
                [x_start, y_start],
                [x_index_stop, y_index_stop],
                [x_stop, y_stop],
            ] = segment

            # Convert back to integers (not very efficient)
            x_index_start = int(round(x_index_start))
            x_index_stop = int(round(x_index_stop))
            y_index_start = int(round(y_index_start))
            y_index_stop = int(round(y_index_stop))

            # For convenience
            x_index_smaller = min(x_index_start, x_index_stop)
            x_index_bigger = max(x_index_start, x_index_stop)
            y_index_smaller = min(y_index_start, y_index_stop)
            y_index_bigger = max(y_index_start, y_index_stop)

            # Slope is inverted because y indices are inverted
            indices_slope: Optional[float] = None
            if x_index_start != x_index_stop:
                indices_slope = (-1 * (y_index_stop - y_index_start) /
                                 (x_index_stop - x_index_start))
            slope: Optional[float] = None
            if x_start != x_stop:
                slope = (y_stop - y_start) / (x_stop - x_start)

            pixels_already_drawn = False
            if indices_slope is None:
                # That means it's a vertical line
                step = 1
                if y_index_stop < y_index_start:
                    step = -1
                pixels[y_index_start:y_index_stop:step, x_index_start] = 1
                pixels_already_drawn = True

            elif y_index_start == y_index_stop:
                # That means it's a horizontal line
                step = 1
                if x_index_stop < x_index_start:
                    step = -1
                pixels[y_index_start, x_index_start:x_index_stop:step] = 1
                pixels_already_drawn = True

            elif abs(indices_slope) > 1:
                # Draw line by iterating vertically
                # 1. Compute y indices in the middle of bins between the two origins
                step = 1
                if y_index_stop < y_index_start:
                    step = -1
                y_indices_of_line = np.arange(y_index_start,
                                              y_index_stop + step,
                                              step=step)
                ys_of_line = invert_discretize(
                    height - 1 - y_indices_of_line,
                    minimum=y_min,
                    maximum=y_max,
                    nr_bins=height,
                )

                # 2. Compute corresponding x coordinates
                # Derivation:
                #   xs_of_line - x_start = (ys_of_line - y_start) / slope
                #   xs_of_line = (ys_of_line - y_start) / slope + x_start
                xs_of_line = (ys_of_line - y_start) / slope + x_start
                x_indices_of_line = discretize(xs_of_line,
                                               x_min=x_min,
                                               x_max=x_max,
                                               steps=width)

            else:
                # Draw line by iterating horizontically
                # 1. Compute x indices in the middle of bins between the two origins
                step = 1
                if x_index_stop < x_index_start:
                    step = -1
                x_indices_of_line = np.arange(x_index_start,
                                              x_index_stop + step,
                                              step=step)
                xs_of_line = invert_discretize(x_indices_of_line,
                                               minimum=x_min,
                                               maximum=x_max,
                                               nr_bins=width)

                # 2. Compute corresponding y coordinates
                ys_of_line = y_start + slope * (xs_of_line - x_start)
                y_indices_of_line = (height - 1 - discretize(
                    ys_of_line, x_min=y_min, x_max=y_max, steps=height))

            # Finally, draw pixels (if needed)
            if not pixels_already_drawn:
                # Assemble pixels
                xy_indices_of_line = np.column_stack(
                    (x_indices_of_line, y_indices_of_line))

                # Filter out of view pixels
                xy_indices_of_line = xy_indices_of_line[
                    (xy_indices_of_line[:, 0] >= max(0, x_index_smaller))
                    & (xy_indices_of_line[:,
                                          0] <= min(width - 1, x_index_bigger))
                    & (xy_indices_of_line[:, 1] >= max(0, y_index_smaller))
                    & (xy_indices_of_line[:, 1] <= min(height - 1,
                                                       y_index_bigger))]
                xy_indices_of_line = xy_indices_of_line.T
                pixels[xy_indices_of_line[1], xy_indices_of_line[0]] = 1

    # Filter out of view pixels
    xy_indices = xy_indices[(xy_indices[:, 0] >= 0)
                            & (xy_indices[:, 0] < width)
                            & (xy_indices[:, 1] >= 0)
                            & (xy_indices[:, 1] < height)]
    xy_indices = xy_indices.T

    # Assemble pixel matrix
    pixels[xy_indices[1], xy_indices[0]] = 1

    return pixels