Skip to content

Commit 138ccd6

Browse files
authored
Merge pull request #45 from fierg/thesis-k3b
Thesis k3b
2 parents 7ce8b7a + 09c8967 commit 138ccd6

11 files changed

Lines changed: 87 additions & 129 deletions

File tree

doc/thesis/src/analyse.tex

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ \section{Initial Findings}
6161
\par{
6262
However after some analysis of the corpus data, it was shown that most runs had a value of one and almost no runs larger that 4 occurred, which lead to the conclusion, two bit for the run count should be plenty, which is also shown in Table \ref{tab:t5 run length eval}.
6363
Even with a run size of just two bits, there is still a increase in size of about 9\% and uses 8.74 $\frac{bits}{symbol}$. This is still useful as a kind of a base line.
64+
Interestingly the binary implementation performs better on 2 bits per RLE number (4 \% increase in size) than the byte implementation (9 \% increase in size) but also worse with a higher amount of bits per run, where it expands the data to more than triple in size. It is unclear which kind of implementation will profit most of preprocessing, so both will be further analyzed.
6465
}
6566

6667
\par{
67-
Interestingly the binary implementation performs better on 2 bits per RLE number (4 \% increase in size) than the byte implementation (9 \% increase in size) but also worse with a higher amount of bits per run, where it expands the data to more than triple in size. It is unclear which kind of implementation will profit most of preprocessing, so both will be further analyzed.
68+
If we take a more detailed look, we can see that while most files expand with larger RLE numbers, some files have their minimum size when encoded with higher RLE numbers of up to 7 bit. With the simple binary based RLE, almost all files of the Calgary Corpus expand linear related to the amount of bits used for the encoding. The file \textit{pic} decreases in size until 7 bits per RLE number used to a sizes of just $19.5 \%$ of its original size with only $1.56 \: \frac{bits}{symbol}$ while the other files just doubled or even tripled in size. Using the byte wise operating RLE we see a similar result but not as decent with $27.2\%$ of its original size using $2.17 \: \frac{bits}{symbol}$ using 6 bits per run. The benefit of the byte wise RLE is the better worst case performance of $1.5$ up to $1.7$ times the original size.
6869
}
6970

71+
7072
%% ==============================
71-
\section{Improvements by Preprocessing}
73+
\section{Possible Improvements by Preprocessing}
7274
%% ==============================
7375
\label{ch:Analysis:sec:Improvements by Preprocessing}
7476

@@ -91,9 +93,26 @@ \subsection{Vertical byte reading}
9193
Instead of performing compute intense operation on the data, we could also interpret the data in a different way and apply the original run length encoding on binary data. By reading the data in chunks of a fixed size, it is possible to read all most significant bits of all bytes, then the second most significant bits of all bytes and so on. This interpretation results in longer runs as shown in the example below.
9294
}
9395

96+
TODO: example
9497

9598
\par{
99+
It is clear that simply a different way of reading the input does not compress the actual data, instead it enables a better application of existing compression. Without any further action, no performance improvements will be made.
100+
}
101+
102+
\subsection{Byte remapping}
103+
\par{
104+
One idea might be a dynamic byte remapping as the input data is read in parts. Some sections have more specific characters or bytes than others, or this idea can be applied to the whole file. This way the values are not alternating in the whole range of 0 to 255 but rather in a smaller subset and the most frequent ones should be mapped to the smallest values.
105+
}
96106

107+
TODO: Example
108+
109+
\par{
110+
111+
}
112+
113+
\subsection{Combined approaches}
114+
\par{
115+
The idea of combining different compression methods into a superior method is not new and was also performed on RLE as mentioned in Section \ref{ch:Principles of compression:sec:Run Length Encoding:subSec:History}. While the idea of encoding the RLE numbers with Huffman codes is already known and analyzed well, the vertical byte reading enables new approaches, even more in combination with the idea of byte remapping.
97116
}
98117

99118

doc/thesis/src/grundlagen.tex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
\chapter{Principles of compression}
66
\label{ch:Principles of compression}
77
%% ==============================
8-
9-
To understand compression, one first has to understand some basic principles of information theory like Entropy and different approaches to compress different types of data with different encoding and entropy. I will also show the key differences between probability coding and dictionary coding and a few comments on lossy compression.
8+
\par{
9+
The basic idea of compression is to remove redundancy in data. Compression can be broken down into two broad categories: Lossless and lossy compression. Lossless compression makes it possible to reproduce the original data exactly while lossy compression allows the some degradation in the encoded data to gain even higher compression at the cost of some of the original information. To understand compression, one first has to understand some basic principles of information theory like Entropy and different approaches to compress different types of data with different encoding. We will also show the key differences between probability coding and dictionary coding.}
1010

1111
%% ==============================
1212
\section{Compression and Encoding fundamentals}
@@ -98,6 +98,7 @@ \section{Run Length Coding}
9898
}
9999

100100
\subsection{The history}
101+
\label{ch:Principles of compression:sec:Run Length Encoding:subSec:History}
101102
\par{
102103
The ITU-T T4 (Group 3) standard for Facsimile (fax) machines \cite{ITU} is still in force for all devices used over regular phone lines. Each transmission sends a black and white image, where each pixel is called a \textit{pel} and with a horizontal resolution of $8.05 \: \frac{pels}{mm}$ and the vertical resolution depending on the mode. To encode each sequence of black and white pixels, the T4 standard uses RLE to encode each sequence of black and white pixels and since there are only two values, only the run length itself has to be encoded. It is assumed that the first run is always a white run, so there is a dummy white pel at the beginning of each sequence. For example, the sequence $bbbbwwbbbbb$ can be encoded as 1,4,2,5 with the leading white dummy pixel.
103104
}
@@ -136,6 +137,12 @@ \subsection{Limitations}
136137
\par{
137138
As mentioned in the Section \ref{ch:Introduction:sec:Problem statement}, run length encoding is rarely used for regular text or continuous tone images because its potentially increase in size, due to non repetitive characters or bytes. To reduce this problem there are several approaches known, partly implemented and used some of which will be addressed like a Burrows-Wheeler-Transformation.
138139
}
140+
141+
\subsection{Run Length Encoding today}
142+
\par{
143+
While it is still in use by fax transmission or other highly specific tasks, it is mostly used in combination with other approaches.
144+
TODO: give more references
145+
}
139146
%% ==============================
140147
\section{Prefix Coding}
141148
%% ==============================

doc/thesis/thesis.pdf

2.89 KB
Binary file not shown.

src/main/kotlin/edu/ba/twoDimensionalRLE/analysis/Analyzer.kt

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,35 @@ class Analyzer() {
8888

8989
}
9090

91-
fun sizeCompare(folderToEncode : String, encodedFolder : String) {
92-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
93-
.reduce { t: Long, u: Long -> t + u }.get()
94-
val sizeEncoded =
95-
Files.walk(File(encodedFolder).toPath()).map { mapper -> mapper.toFile().length() }
96-
.reduce { t: Long, u: Long -> t + u }.get()
91+
fun sizeCompare(folderToEncode: String, encodedFolder: String) {
92+
val originalFiles = mutableMapOf<File, Long>()
93+
Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile() to mapper.toFile().length() }
94+
.forEach {
95+
originalFiles[it.first] = it.second
96+
}
97+
val sizeOriginal = originalFiles.map { it.value }.reduce { l: Long?, l2: Long? -> l!! + l2!! }
98+
99+
val encodedFiles = mutableMapOf<File, Long>()
100+
Files.walk(File(encodedFolder).toPath()).map { mapper -> mapper.toFile() to mapper.toFile().length() }.forEach {
101+
encodedFiles[it.first] = it.second
102+
}
103+
val sizeEncoded = encodedFiles.map { it.value }.reduce { t: Long, u: Long -> t + u }
104+
97105
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
98106

99107
log.info("Corpus size original: ${sizeOriginal / 1000000.0} Mb")
100108
log.info("Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
101109

102110
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
103111
log.info("with $bitsPerSymbol bits/symbol")
112+
113+
114+
originalFiles.filter { it.key.isFile }.forEach { original ->
115+
val encodedFile = encodedFiles.filterKeys { it.nameWithoutExtension == original.key.nameWithoutExtension }
116+
val bitsPerSymbolFile = (encodedFile.values.first() * 8).toDouble() / original.value.toDouble()
117+
118+
log.info("File ${original.key.name}, size encoded: ${encodedFile.values.first()}, size original: ${original.value}, compression: ${encodedFile.values.first().toDouble() / original.value.toDouble()}, bps: $bitsPerSymbolFile")
119+
}
104120
}
105121

106122
}

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus2BitTest.kt

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -68,24 +69,9 @@ class StringRLECorpus2BitTest {
6869
)
6970
}
7071

71-
}
72-
73-
@Test
74-
@Order(3)
75-
fun size() {
76-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
77-
.reduce { t: Long, u: Long -> t + u }.get()
78-
val sizeEncoded =
79-
Files.walk(File("$encodeFolder/CalgaryCorpus").toPath()).map { mapper -> mapper.toFile().length() }
80-
.reduce { t: Long, u: Long -> t + u }.get()
81-
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
82-
83-
log.info("Galgary Corpus size original: ${sizeOriginal / 1000000.0} Mb")
84-
log.info("Galgary Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
85-
86-
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
87-
log.info("with $bitsPerSymbol bits/symbol")
88-
}
72+
val analyzer = Analyzer()
73+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
8974

9075

76+
}
9177
}

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus3BitTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -68,6 +69,8 @@ class StringRLECorpus3BitTest {
6869
)
6970
}
7071

72+
val analyzer = Analyzer()
73+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
7174
}
7275

7376
@Test

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus4BitTest.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -58,7 +59,7 @@ class StringRLECorpus4BitTest {
5859
"$folderToEncode/${it.name}", "$encodeFolder/CalgaryCorpus/${it.name}.rle",
5960
applyByteMapping = applyByteMapping,
6061
applyBurrowsWheelerTransformation = applyBWT,
61-
bitPerRun = bitsPerRleNumber , chunkSize = 256
62+
bitPerRun = bitsPerRleNumber, chunkSize = 256
6263
)
6364
strRLE.decodeVarLength(
6465
"$encodeFolder/CalgaryCorpus/${it.name}.rle", "$decodeFolder/CalgaryCorpus/${it.name}",
@@ -67,25 +68,8 @@ class StringRLECorpus4BitTest {
6768
bitPerRun = bitsPerRleNumber, chunkSize = 256
6869
)
6970
}
71+
val analyzer = Analyzer()
72+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
7073

7174
}
72-
73-
@Test
74-
@Order(3)
75-
fun size() {
76-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
77-
.reduce { t: Long, u: Long -> t + u }.get()
78-
val sizeEncoded =
79-
Files.walk(File("$encodeFolder/CalgaryCorpus").toPath()).map { mapper -> mapper.toFile().length() }
80-
.reduce { t: Long, u: Long -> t + u }.get()
81-
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
82-
83-
log.info("Calgary Corpus size original: ${sizeOriginal / 1000000.0} Mb")
84-
log.info("Calgary Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
85-
86-
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
87-
log.info("with $bitsPerSymbol bits/symbol")
88-
}
89-
90-
9175
}

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus5BitTest.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -58,34 +59,19 @@ class StringRLECorpus5BitTest {
5859
"$folderToEncode/${it.name}", "$encodeFolder/CalgaryCorpus/${it.name}.rle",
5960
applyByteMapping = applyByteMapping,
6061
applyBurrowsWheelerTransformation = applyBWT,
61-
bitPerRun = bitsPerRleNumber , chunkSize = 256
62+
bitPerRun = bitsPerRleNumber, chunkSize = 256
6263
)
6364
strRLE.decodeVarLength(
6465
"$encodeFolder/CalgaryCorpus/${it.name}.rle", "$decodeFolder/CalgaryCorpus/${it.name}",
6566
applyByteMapping = applyByteMapping,
6667
applyBurrowsWheelerTransformation = applyBWT,
6768
bitPerRun = bitsPerRleNumber
68-
, chunkSize = 256)
69+
, chunkSize = 256
70+
)
6971
}
72+
val analyzer = Analyzer()
73+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
7074

7175
}
7276

73-
@Test
74-
@Order(3)
75-
fun size() {
76-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
77-
.reduce { t: Long, u: Long -> t + u }.get()
78-
val sizeEncoded =
79-
Files.walk(File("$encodeFolder/CalgaryCorpus").toPath()).map { mapper -> mapper.toFile().length() }
80-
.reduce { t: Long, u: Long -> t + u }.get()
81-
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
82-
83-
log.info("Calgary Corpus size original: ${sizeOriginal / 1000000.0} Mb")
84-
log.info("Calgary Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
85-
86-
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
87-
log.info("with $bitsPerSymbol bits/symbol")
88-
}
89-
90-
9177
}

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus6BitTest.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -58,34 +59,19 @@ class StringRLECorpus6BitTest {
5859
"$folderToEncode/${it.name}", "$encodeFolder/CalgaryCorpus/${it.name}.rle",
5960
applyByteMapping = applyByteMapping,
6061
applyBurrowsWheelerTransformation = applyBWT,
61-
bitPerRun = bitsPerRleNumber , chunkSize = 256
62+
bitPerRun = bitsPerRleNumber, chunkSize = 256
6263
)
6364
strRLE.decodeVarLength(
6465
"$encodeFolder/CalgaryCorpus/${it.name}.rle", "$decodeFolder/CalgaryCorpus/${it.name}",
6566
applyByteMapping = applyByteMapping,
6667
applyBurrowsWheelerTransformation = applyBWT,
6768
bitPerRun = bitsPerRleNumber
68-
, chunkSize = 256)
69+
, chunkSize = 256
70+
)
6971
}
7072

73+
val analyzer = Analyzer()
74+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
7175
}
7276

73-
@Test
74-
@Order(3)
75-
fun size() {
76-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
77-
.reduce { t: Long, u: Long -> t + u }.get()
78-
val sizeEncoded =
79-
Files.walk(File("$encodeFolder/CalgaryCorpus").toPath()).map { mapper -> mapper.toFile().length() }
80-
.reduce { t: Long, u: Long -> t + u }.get()
81-
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
82-
83-
log.info("Calgary Corpus size original: ${sizeOriginal / 1000000.0} Mb")
84-
log.info("Calgary Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
85-
86-
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
87-
log.info("with $bitsPerSymbol bits/symbol")
88-
}
89-
90-
9177
}

src/test/kotlin/edu/ba/twoDimensionalRLE/rle/StringRLECorpus7BitTest.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package edu.ba.twoDimensionalRLE.rle
22

33

44
import de.jupf.staticlog.Log
5+
import edu.ba.twoDimensionalRLE.analysis.Analyzer
56
import edu.ba.twoDimensionalRLE.encoder.rle.StringRunLengthEncoder
67
import org.junit.jupiter.api.MethodOrderer
78
import org.junit.jupiter.api.Order
@@ -58,34 +59,19 @@ class StringRLECorpus7BitTest {
5859
"$folderToEncode/${it.name}", "$encodeFolder/CalgaryCorpus/${it.name}.rle",
5960
applyByteMapping = applyByteMapping,
6061
applyBurrowsWheelerTransformation = applyBWT,
61-
bitPerRun = bitsPerRleNumber , chunkSize = 256
62+
bitPerRun = bitsPerRleNumber, chunkSize = 256
6263
)
6364
strRLE.decodeVarLength(
6465
"$encodeFolder/CalgaryCorpus/${it.name}.rle", "$decodeFolder/CalgaryCorpus/${it.name}",
6566
applyByteMapping = applyByteMapping,
6667
applyBurrowsWheelerTransformation = applyBWT,
6768
bitPerRun = bitsPerRleNumber
68-
, chunkSize = 256)
69+
, chunkSize = 256
70+
)
6971
}
7072

73+
val analyzer = Analyzer()
74+
analyzer.sizeCompare(folderToEncode, "${encodeFolder}/CalgaryCorpus")
7175
}
7276

73-
@Test
74-
@Order(3)
75-
fun size() {
76-
val sizeOriginal = Files.walk(File(folderToEncode).toPath()).map { mapper -> mapper.toFile().length() }
77-
.reduce { t: Long, u: Long -> t + u }.get()
78-
val sizeEncoded =
79-
Files.walk(File("$encodeFolder/CalgaryCorpus").toPath()).map { mapper -> mapper.toFile().length() }
80-
.reduce { t: Long, u: Long -> t + u }.get()
81-
val bitsPerSymbol = (sizeEncoded * 8).toDouble() / sizeOriginal.toDouble()
82-
83-
log.info("Calgary Corpus size original: ${sizeOriginal / 1000000.0} Mb")
84-
log.info("Calgary Corpus size encoded: ${sizeEncoded / 1000000.0} Mb")
85-
86-
log.info("${sizeEncoded.toDouble() / sizeOriginal.toDouble()} compression ratio")
87-
log.info("with $bitsPerSymbol bits/symbol")
88-
}
89-
90-
9177
}

0 commit comments

Comments
 (0)