Skip to content

Commit a01a24c

Browse files
authored
Merge pull request OUDON#39 from OUDON/refactor/use-block-class
refactor: Refactoring the rMQR class
2 parents 0549eef + b20ee9c commit a01a24c

File tree

1 file changed

+226
-50
lines changed

1 file changed

+226
-50
lines changed

src/rmqrcode/rmqrcode.py

Lines changed: 226 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ def sort_key(x):
127127
return qr
128128

129129
def _optimized_segments(self, data):
130+
"""Returns optimized segments computed by SegmentOptimizer.
131+
132+
Args:
133+
data (str): The data to encode.
134+
135+
Returns:
136+
list: The list of segments.
137+
138+
"""
130139
optimizer = qr_segments.SegmentOptimizer()
131140
return optimizer.compute(data, self.version_name(), self._error_correction_level)
132141

@@ -161,6 +170,15 @@ def add_segment(self, data, encoder_class=encoder.ByteEncoder):
161170
self._segments.append({"data": data, "encoder_class": encoder_class})
162171

163172
def add_segments(self, segments):
173+
"""Add the segments.
174+
175+
Args:
176+
segments (list): The list of segments.
177+
178+
Returns:
179+
void
180+
181+
"""
164182
for segment in segments:
165183
self.add_segment(segment["data"], segment["encoder_class"])
166184

@@ -454,11 +472,24 @@ def _put_timing_pattern(self):
454472
self._qr[i][j] = color
455473

456474
def _put_version_information(self):
475+
"""Version information placement."""
457476
version_information = self._compute_version_info()
458477
self._put_version_information_finder_pattern_side(version_information)
459478
self._put_version_information_finder_sub_pattern_side(version_information)
460479

461480
def _put_version_information_finder_pattern_side(self, version_information):
481+
"""Version information placement (finder pattern side).
482+
483+
This method computes masked version information data and puts it. The mask
484+
pattern is 011111101010110010.
485+
486+
Args:
487+
version_information (int): The version information.
488+
489+
Returns:
490+
void
491+
492+
"""
462493
mask = 0b011111101010110010
463494
version_information ^= mask
464495

@@ -469,6 +500,18 @@ def _put_version_information_finder_pattern_side(self, version_information):
469500
self._qr[si + di][sj + dj] = Color.BLACK if version_information >> n & 1 else Color.WHITE
470501

471502
def _put_version_information_finder_sub_pattern_side(self, version_information):
503+
"""Version information placement (finder sub pattern side).
504+
505+
This method computes masked version information data and puts it. The mask
506+
pattern is 100000101001111011.
507+
508+
Args:
509+
version_information (int): The version information.
510+
511+
Returns:
512+
void
513+
514+
"""
472515
mask = 0b100000101001111011
473516
version_information ^= mask
474517

@@ -488,6 +531,7 @@ def _put_version_information_finder_sub_pattern_side(self, version_information):
488531
)
489532

490533
def _compute_version_info(self):
534+
"""Computes version information with BCH code."""
491535
qr_version = rMQRVersions[self.version_name()]
492536
version_information_data = qr_version["version_indicator"]
493537
if self._error_correction_level == ErrorCorrectionLevel.H:
@@ -512,47 +556,135 @@ def _put_data(self, encoded_data):
512556
list: A two-dimensional list shows where encoding region.
513557
514558
"""
515-
codewords = split_into_8bits(encoded_data)
516-
517-
# Add the remainder codewords
518559
qr_version = rMQRVersions[self.version_name()]
519-
codewords_total = qr_version["codewords_total"]
560+
codewords_num = qr_version["codewords_total"]
561+
562+
codewords = self._make_codewords(encoded_data, codewords_num)
563+
blocks = self._split_into_blocks(codewords, qr_version["blocks"][self._error_correction_level])
564+
final_codewords = self._make_final_codewords(blocks)
565+
mask = self._put_final_codewords(final_codewords, qr_version["remainder_bits"])
566+
return mask
567+
568+
def _make_codewords(self, encoded_data, codewords_num):
569+
"""Makes codeword sequence from encoded data.
570+
571+
If the length of generated codeword sequence is less than the `codewords_num`,
572+
appends the reminder codewords 11101100 and 00010001 alternately to meet the
573+
requirements of number of codewords.
574+
575+
Args:
576+
encoded_data (str): The encoded data.
577+
codewords_num (int): The number of codewords.
578+
579+
Returns:
580+
list: The list of codeword strings.
581+
582+
"""
583+
codewords = split_into_8bits(encoded_data)
520584
while True:
521-
if len(codewords) >= codewords_total:
585+
if len(codewords) >= codewords_num:
522586
break
523587
codewords.append("11101100")
524-
if len(codewords) >= codewords_total:
588+
if len(codewords) >= codewords_num:
525589
break
526590
codewords.append("00010001")
591+
return codewords
527592

528-
data_codewords_per_block, rs_codewords_per_block = self._split_into_blocks(
529-
codewords, qr_version["blocks"][self._error_correction_level]
530-
)
593+
def _split_into_blocks(self, codewords, blocks_definition):
594+
"""Splits codewords into several blocks.
595+
596+
Args:
597+
codewords (list): The list of codeword strings.
598+
blocks_definition: The list of dict.
599+
600+
Returns:
601+
list: The list of Block object.
602+
603+
"""
604+
data_idx = 0
605+
blocks = []
606+
for block_definition in blocks_definition:
607+
for i in range(block_definition["num"]):
608+
data_codewords_num = block_definition["k"]
609+
ecc_codewords_num = block_definition["c"] - block_definition["k"]
610+
codewords_in_block = codewords[data_idx : data_idx + data_codewords_num]
611+
block = Block(data_codewords_num, ecc_codewords_num)
612+
block.set_data_and_compute_ecc(codewords_in_block)
613+
blocks.append(block)
614+
data_idx += data_codewords_num
615+
return blocks
616+
617+
def _make_final_codewords(self, blocks):
618+
"""Makes the final message codeword sequence.
619+
620+
This method computes the final codeword sequence from the given blocks. For example,
621+
we consider the following blocks. The blocks consists of three blocks. Block1 contains
622+
two data blocks and three ecc blocks. Block2 contains three data blocks and three ecc blocks.
623+
Block3 contains three data blocks and three ecc blocks.
624+
625+
Block1: Data#1 Data#2 ------ Ecc#1 Ecc#2 Ecc#3
626+
Block2: Data#3 Data#4 Data#5 Ecc#4 Ecc#5 Ecc#6
627+
Block3: Data#6 Data#7 Data#8 Ecc#7 Ecc#8 Ecc#9
628+
629+
The final codeword sequence for this example is placed in the following order.
531630
532-
# Construct the final message codeword sequence
533-
# Data codewords
631+
[Data#1, Data#3, Data#6, Data#2, Data#4, Data#7, Data#5, Data#8,
632+
Ecc#1, Ecc#4, Ecc#7, Ecc#2, Ecc#5, Ecc#8, Ecc#3, Ecc#6, Ecc#9]
633+
634+
Args:
635+
blocks (list): The list of Block objects.
636+
637+
Returns:
638+
list: The list of codeword strings.
639+
640+
"""
534641
final_codewords = []
535-
for i in range(len(data_codewords_per_block[-1])):
536-
for data_codewords in data_codewords_per_block:
537-
if i >= len(data_codewords):
538-
continue
539-
final_codewords.append(data_codewords[i])
540-
self._logger.debug(f"Put QR data codeword {i} : {data_codewords[i]}")
642+
# Add data codewords
643+
# The last block always has the most codewords.
644+
for i in range(blocks[-1].data_length()):
645+
for block in blocks:
646+
try:
647+
data_codeword = block.get_data_at(i)
648+
except IndexError:
649+
break
650+
else:
651+
final_codewords.append(data_codeword)
652+
self._logger.debug(f"Put QR data codeword {i} : {data_codeword}")
653+
654+
# Add ecc codewords
655+
# The last block always has the most codewords.
656+
for i in range(blocks[-1].ecc_length()):
657+
for block in blocks:
658+
try:
659+
ecc_codeword = block.get_ecc_at(i)
660+
except IndexError:
661+
break
662+
else:
663+
final_codewords.append(ecc_codeword)
664+
self._logger.debug(f"Put RS data codewords {i} : {ecc_codeword}")
665+
return final_codewords
541666

542-
# RS Codewords
543-
for i in range(len(rs_codewords_per_block[-1])):
544-
for rs_codewords in rs_codewords_per_block:
545-
if i >= len(rs_codewords):
546-
continue
547-
final_codewords.append(rs_codewords[i])
548-
self._logger.debug(f"Put RS data codewords {i} : {rs_codewords[i]}")
667+
def _put_final_codewords(self, final_codewords, reminder_bits_num):
668+
"""Puts the final codeword sequence.
549669
550-
# Codeword placement
670+
This method puts the final codeword sequence into the encoding region of the rMQR Code.
671+
The `final_codewords` is computed by self._make_final_codewords method. Also, this method
672+
computes a two-dimensional list shows where encoding region at the same time.
673+
And returns the list.
674+
675+
Args:
676+
final_codewords (list): The list of the final codeword strings.
677+
reminder_bits_num (int): The number of modules without data.
678+
679+
Returns:
680+
list: A two-dimensional list shows where encoding region.
681+
682+
"""
551683
dy = -1 # Up
552684
current_codeword_idx = 0
553685
current_bit_idx = 0
554686
cx, cy = self._width - 2, self._height - 6
555-
remainder_bits = qr_version["remainder_bits"]
687+
remaining_remainder_bits = reminder_bits_num
556688
mask_area = [[False for i in range(self._width)] for j in range(self._height)]
557689

558690
while True:
@@ -563,7 +695,7 @@ def _put_data(self, encoded_data):
563695
# Remainder bits
564696
self._qr[cy][x] = Color.WHITE
565697
mask_area[cy][x] = True
566-
remainder_bits -= 1
698+
remaining_remainder_bits -= 1
567699
else:
568700
# Codewords
569701
self._qr[cy][x] = (
@@ -577,10 +709,10 @@ def _put_data(self, encoded_data):
577709
current_bit_idx = 0
578710
current_codeword_idx += 1
579711

580-
if current_codeword_idx == len(final_codewords) and remainder_bits == 0:
712+
if current_codeword_idx == len(final_codewords) and remaining_remainder_bits == 0:
581713
break
582714

583-
if current_codeword_idx == len(final_codewords) and remainder_bits == 0:
715+
if current_codeword_idx == len(final_codewords) and remaining_remainder_bits == 0:
584716
break
585717

586718
# Update current coordinates
@@ -595,27 +727,6 @@ def _put_data(self, encoded_data):
595727

596728
return mask_area
597729

598-
def _split_into_blocks(self, codewords, blocks_definition):
599-
data_idx, error_idx = 0, 0
600-
data_codewords_per_block = []
601-
rs_codewords_per_block = []
602-
for block_definition in blocks_definition:
603-
for i in range(block_definition["num"]):
604-
data_codewords_num = block_definition["k"]
605-
rs_codewords_num = block_definition["c"] - block_definition["k"]
606-
g = GeneratorPolynomials[rs_codewords_num]
607-
608-
codewords_in_block = codewords[data_idx : data_idx + data_codewords_num]
609-
rs_codewords_in_block = compute_reed_solomon(codewords_in_block, g, rs_codewords_num)
610-
611-
data_codewords_per_block.append(codewords_in_block)
612-
rs_codewords_per_block.append(rs_codewords_in_block)
613-
614-
data_idx += data_codewords_num
615-
error_idx += rs_codewords_num
616-
617-
return data_codewords_per_block, rs_codewords_per_block
618-
619730
def _apply_mask(self, mask_area):
620731
"""Data masking.
621732
@@ -661,3 +772,68 @@ def validate_version(version_name):
661772
662773
"""
663774
return version_name in rMQRVersions
775+
776+
777+
class Block:
778+
"""A class represents data block.
779+
780+
This class represents data block. A block consists data part and error correction
781+
code (ecc) part.
782+
783+
"""
784+
785+
def __init__(self, data_codewords_num, ecc_codewords_num):
786+
self._data_codewords_num = data_codewords_num
787+
self._data_codewords = []
788+
self._ecc_codewords_num = ecc_codewords_num
789+
self._ecc_codewords = []
790+
791+
def set_data_and_compute_ecc(self, data_codewords):
792+
"""Set data and compute ecc.
793+
794+
Args:
795+
data_codewords (list): The list of codeword strings.
796+
797+
Returns:
798+
void
799+
800+
"""
801+
self._data_codewords = data_codewords
802+
self._compute_ecc_codewords()
803+
804+
def get_data_at(self, index):
805+
"""Get data codeword at the index.
806+
807+
Args:
808+
index (int): The index.
809+
810+
Return:
811+
str: The data codeword.
812+
813+
"""
814+
return self._data_codewords[index]
815+
816+
def get_ecc_at(self, index):
817+
"""Get ecc codeword at the index.
818+
819+
Args:
820+
index (int): The index.
821+
822+
Return:
823+
str: The ecc codeword.
824+
825+
"""
826+
return self._ecc_codewords[index]
827+
828+
def data_length(self):
829+
"""Get the number of data codewords"""
830+
return len(self._data_codewords)
831+
832+
def ecc_length(self):
833+
"""Get the number of ecc codewords"""
834+
return len(self._ecc_codewords)
835+
836+
def _compute_ecc_codewords(self):
837+
"""Computes the ecc codewords with the data codewords."""
838+
g = GeneratorPolynomials[self._ecc_codewords_num]
839+
self._ecc_codewords = compute_reed_solomon(self._data_codewords, g, self._ecc_codewords_num)

0 commit comments

Comments
 (0)