mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2025-07-09 12:23:52 +00:00
Update comments and fix some bugs
make_edit_distance - use MASKED_DOTS since it does not have a simple relationship to the HEADER_BITS/BODY_BITS for HGR - try disabling transposition distances for Damerau-Levenshtein, this may give better quality screen - introduce separate notion of MASKED_DOTS which is the number of (coloured) pixels we can extract from MASKED_BITS. For HGR this is not the same. - fix bug in _fix_array_neighbours that was not fixing headers for HGR - don't cache everything in byte_pair_differences, it's effectively unbounded. Using 1M for LRU size seems to work just as well in practise, without leaking memory. - fix bug in _diff_weights when comparing content, we want to evaluate the effect of storing content byte in each offset separately, not cumulatively. - add a consistency check function (not currently wired up) to assert that headers/footers are in sync across columns - HGR should have 16 body bits, this was causing headers not to propagate correctly to/from neighbouring column - add test case for this bug video - Use 8 random bits consistently, using 16 in some places may have introduced bias - ignore palette bit when comparing 0x00 and 0x7f in sanity check
This commit is contained in:
@ -15,62 +15,6 @@ import colours
|
||||
import palette
|
||||
import screen
|
||||
|
||||
# The DHGR display encodes 7 pixels across interleaved 4-byte sequences
|
||||
# of AUX and MAIN memory, as follows:
|
||||
#
|
||||
# PBBBAAAA PDDCCCCB PFEEEEDD PGGGGFFF
|
||||
# Aux N Main N Aux N+1 Main N+1 (N even)
|
||||
#
|
||||
# Where A..G are the pixels, and P represents the (unused) palette bit.
|
||||
#
|
||||
# This layout makes more sense when written as a (little-endian) 32-bit integer:
|
||||
#
|
||||
# 33222222222211111111110000000000 <- bit pos in uint32
|
||||
# 10987654321098765432109876543210
|
||||
# PGGGGFFFPFEEEEDDPDDCCCCBPBBBAAAA
|
||||
#
|
||||
# i.e. apart from the palette bits this is a linear ordering of pixels,
|
||||
# when read from LSB to MSB (i.e. right-to-left). i.e. the screen layout order
|
||||
# of bits is opposite to the usual binary representation ordering.
|
||||
#
|
||||
# If we now look at the effect of storing a byte in each of the 4
|
||||
# byte-offset positions within this uint32,
|
||||
#
|
||||
# PGGGGFFFPFEEEEDDPDDCCCCBPBBBAAAA
|
||||
# 33333333222222221111111100000000
|
||||
#
|
||||
# We see that these byte offsets cause changes to the following pixels:
|
||||
#
|
||||
# 0: A B
|
||||
# 1: B C D
|
||||
# 2: D E F
|
||||
# 3: F G
|
||||
#
|
||||
# i.e. DHGR byte stores to offsets 0 and 3 result in changing one 8-bit value
|
||||
# (2 DHGR pixels) into another; offsets 1 and 3 result in changing one 12-bit
|
||||
# value (3 DHGR pixels).
|
||||
#
|
||||
# We can simplify things by stripping out the palette bit and packing
|
||||
# down to a 28-bit integer representation:
|
||||
#
|
||||
# 33222222222211111111110000000000 <- bit pos in uint32
|
||||
# 10987654321098765432109876543210
|
||||
#
|
||||
# 0000GGGGFFFFEEEEDDDDCCCCBBBBAAAA <- pixel A..G
|
||||
# 3210321032103210321032103210 <- bit pos in A..G pixel
|
||||
#
|
||||
# 3333333222222211111110000000 <- byte offset 0.3
|
||||
#
|
||||
# With this representation, we can precompute an edit distance for the
|
||||
# pixel changes resulting from all possible DHGR byte stores.
|
||||
#
|
||||
# We further encode these (source, target) -> distance mappings by
|
||||
# concatenating source and target into 16- or 24-bit values. This is
|
||||
# efficient to work with in the video transcoder.
|
||||
#
|
||||
# Since we are enumerating all such 16- or 24-bit values, these can be packed
|
||||
# contiguously into an array whose index is the (source, target) pair and
|
||||
# the value is the edit distance.
|
||||
|
||||
PIXEL_CHARS = "0123456789ABCDEF"
|
||||
|
||||
@ -85,6 +29,8 @@ def pixel_string(pixels: Iterable[int]) -> str:
|
||||
|
||||
|
||||
class EditDistanceParams:
|
||||
"""Data class for parameters to Damerau-Levenshtein edit distance."""
|
||||
|
||||
# Don't even consider insertions and deletions into the string, they don't
|
||||
# make sense for comparing pixel strings
|
||||
insert_costs = np.ones(128, dtype=np.float64) * 100000
|
||||
@ -92,20 +38,26 @@ class EditDistanceParams:
|
||||
|
||||
# Smallest substitution value is ~20 from palette.diff_matrices, i.e.
|
||||
# we always prefer to transpose 2 pixels rather than substituting colours.
|
||||
transpose_costs = np.ones((128, 128), dtype=np.float64) * 10
|
||||
# TODO: is quality really better allowing transposes?
|
||||
transpose_costs = np.ones((128, 128), dtype=np.float64) * 100000 # 10
|
||||
|
||||
# These will be filled in later
|
||||
substitute_costs = np.zeros((128, 128), dtype=np.float64)
|
||||
|
||||
# Substitution costs to use when evaluating other potential offsets at which
|
||||
# to store a content byte. We penalize more harshly for introducing
|
||||
# errors that alter pixel colours, since these tend to be very
|
||||
# noticeable as visual noise.
|
||||
#
|
||||
# TODO: currently unused
|
||||
error_substitute_costs = np.zeros((128, 128), dtype=np.float64)
|
||||
|
||||
|
||||
def compute_diff_matrix(pal: Type[palette.BasePalette]):
|
||||
# Compute matrix of CIE2000 delta values for this pal, representing
|
||||
# perceptual distance between colours.
|
||||
"""Compute matrix of perceptual distance between colour pairs.
|
||||
|
||||
Specifically CIE2000 delta values for this palette.
|
||||
"""
|
||||
dm = np.ndarray(shape=(16, 16), dtype=np.int)
|
||||
|
||||
for colour1, a in pal.RGB.items():
|
||||
@ -120,6 +72,8 @@ def compute_diff_matrix(pal: Type[palette.BasePalette]):
|
||||
|
||||
|
||||
def compute_substitute_costs(pal: Type[palette.BasePalette]):
|
||||
"""Compute costs for substituting one colour pixel for another."""
|
||||
|
||||
edp = EditDistanceParams()
|
||||
|
||||
diff_matrix = compute_diff_matrix(pal)
|
||||
@ -128,10 +82,10 @@ def compute_substitute_costs(pal: Type[palette.BasePalette]):
|
||||
for i, c in enumerate(PIXEL_CHARS):
|
||||
for j, d in enumerate(PIXEL_CHARS):
|
||||
cost = diff_matrix[i, j]
|
||||
edp.substitute_costs[(ord(c), ord(d))] = cost # / 20
|
||||
edp.substitute_costs[(ord(d), ord(c))] = cost # / 20
|
||||
edp.error_substitute_costs[(ord(c), ord(d))] = 5 * cost # / 4
|
||||
edp.error_substitute_costs[(ord(d), ord(c))] = 5 * cost # / 4
|
||||
edp.substitute_costs[(ord(c), ord(d))] = cost
|
||||
edp.substitute_costs[(ord(d), ord(c))] = cost
|
||||
edp.error_substitute_costs[(ord(c), ord(d))] = 5 * cost
|
||||
edp.error_substitute_costs[(ord(d), ord(c))] = 5 * cost
|
||||
|
||||
return edp
|
||||
|
||||
@ -141,6 +95,7 @@ def edit_distance(
|
||||
a: str,
|
||||
b: str,
|
||||
error: bool) -> np.float64:
|
||||
"""Damerau-Levenshtein edit distance between two pixel strings."""
|
||||
res = weighted_levenshtein.dam_lev(
|
||||
a, b,
|
||||
insert_costs=edp.insert_costs,
|
||||
@ -149,7 +104,8 @@ def edit_distance(
|
||||
edp.error_substitute_costs if error else edp.substitute_costs),
|
||||
)
|
||||
|
||||
assert res == 0 or (1 <= res < 2 ** 16), res
|
||||
# Make sure result can fit in a uint16
|
||||
assert (0 <= res < 2 ** 16), res
|
||||
return res
|
||||
|
||||
|
||||
@ -158,6 +114,19 @@ def compute_edit_distance(
|
||||
bitmap_cls: Type[screen.Bitmap],
|
||||
nominal_colours: Type[colours.NominalColours]
|
||||
):
|
||||
"""Computes edit distance matrix between all pairs of pixel strings.
|
||||
|
||||
Enumerates all possible values of the masked bit representation from
|
||||
bitmap_cls (assuming it is contiguous, i.e. we enumerate all
|
||||
2**bitmap_cls.MASKED_BITS values). These are mapped to the dot
|
||||
representation, turned into coloured pixel strings, and we compute the
|
||||
edit distance.
|
||||
|
||||
The effect of this is that we precompute the effect of storing all possible
|
||||
byte values against all possible screen backgrounds (e.g. as
|
||||
influencing/influenced by neighbouring bytes).
|
||||
"""
|
||||
|
||||
bits = bitmap_cls.MASKED_BITS
|
||||
|
||||
bitrange = np.uint64(2 ** bits)
|
||||
@ -171,7 +140,7 @@ def compute_edit_distance(
|
||||
# triangle
|
||||
bar = ProgressBar((bitrange * (bitrange - 1)) / 2, max_width=80)
|
||||
|
||||
num_dots = bitmap_cls.HEADER_BITS + bitmap_cls.BODY_BITS
|
||||
num_dots = bitmap_cls.MASKED_DOTS
|
||||
|
||||
cnt = 0
|
||||
for i in range(np.uint64(bitrange)):
|
||||
@ -211,6 +180,8 @@ def make_edit_distance(
|
||||
bitmap_cls: Type[screen.Bitmap],
|
||||
nominal_colours: Type[colours.NominalColours]
|
||||
):
|
||||
"""Write file containing (D)HGR edit distance matrix for a palette."""
|
||||
|
||||
dist = compute_edit_distance(edp, bitmap_cls, nominal_colours)
|
||||
data = "transcoder/data/%s_palette_%d_edit_distance.pickle.bz2" % (
|
||||
bitmap_cls.NAME, pal.ID.value)
|
||||
@ -223,7 +194,7 @@ def main():
|
||||
print("Processing palette %s" % p)
|
||||
edp = compute_substitute_costs(p)
|
||||
|
||||
# TODO: error distance matrices
|
||||
# TODO: still worth using error distance matrices?
|
||||
|
||||
make_edit_distance(p, edp, screen.HGRBitmap, colours.HGRColours)
|
||||
make_edit_distance(p, edp, screen.DHGRBitmap, colours.DHGRColours)
|
||||
|
Reference in New Issue
Block a user