Joshua Moerman
11 years ago
18 changed files with 408 additions and 301 deletions
@ -0,0 +1,2 @@ |
|||||
|
#define NEXP 20 |
||||
|
|
@ -0,0 +1,134 @@ |
|||||
|
#include <includes.hpp> |
||||
|
#include <utilities.hpp> |
||||
|
|
||||
|
#include <boost/filesystem.hpp> |
||||
|
#include <png.hpp> |
||||
|
|
||||
|
#include "jcmp.hpp" |
||||
|
#include "wavelet.hpp" |
||||
|
|
||||
|
using namespace wvlt::V2; |
||||
|
|
||||
|
// Can be any functions from R+ -> R
|
||||
|
// as long as backward is its inverse
|
||||
|
static double forward(double x){ |
||||
|
//return std::log(1 + std::log(1 + x));
|
||||
|
return std::log(x); |
||||
|
} |
||||
|
|
||||
|
static double backward(double x){ |
||||
|
//return std::exp(std::exp(x) - 1) - 1;
|
||||
|
return std::exp(x); |
||||
|
} |
||||
|
|
||||
|
// note: we take a copy, because we will modify it in place
|
||||
|
static jcmp::image compress(std::vector<double> image, jcmp::uint width, double threshold, unsigned int& nzeros){ |
||||
|
jcmp::uint height = image.size() / width; |
||||
|
assert(is_pow_of_two(width)); |
||||
|
assert(is_pow_of_two(height)); |
||||
|
|
||||
|
// wavelet transform in x-direction
|
||||
|
for(unsigned int i = 0; i < height; ++i){ |
||||
|
wavelet(&image[i*width], width, 1); |
||||
|
} |
||||
|
|
||||
|
// wavelet transform in y-direction
|
||||
|
for(unsigned int i = 0; i < width; ++i){ |
||||
|
wavelet(&image[i], height, width); |
||||
|
} |
||||
|
|
||||
|
double min_abs = 10000.0; |
||||
|
double max_abs = 0.0; |
||||
|
|
||||
|
for(auto& el : image){ |
||||
|
auto absel = std::abs(el); |
||||
|
if(absel > threshold) { |
||||
|
min_abs = std::min(min_abs, absel); |
||||
|
max_abs = std::max(max_abs, absel); |
||||
|
} else { |
||||
|
el = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
jcmp::quantization q(&forward, &backward, max_abs, min_abs); |
||||
|
|
||||
|
// save the principal coefficients
|
||||
|
std::vector<jcmp::coefficient> v; |
||||
|
for(unsigned int y = 0; y < height; ++y){ |
||||
|
for(unsigned int x = 0; x < width; ++x){ |
||||
|
auto&& el = image[x + width*y]; |
||||
|
if(el != 0) v.push_back({q.forwards(el), jcmp::uint(x), jcmp::uint(y)}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
nzeros = v.size(); |
||||
|
return jcmp::image(width, height, q.p, std::move(v)); |
||||
|
} |
||||
|
|
||||
|
static std::vector<double> decompress(jcmp::image in){ |
||||
|
auto width = in.header.width; |
||||
|
auto height = in.header.height; |
||||
|
assert(is_pow_of_two(width)); |
||||
|
assert(is_pow_of_two(height)); |
||||
|
|
||||
|
auto q = in.header.get_quantization(&forward, &backward); |
||||
|
|
||||
|
std::vector<double> image(width * height, 0.0); |
||||
|
|
||||
|
// read in coefficient on coordinates
|
||||
|
for(auto it = in.data.begin(); it != in.data.end(); ++it){ |
||||
|
auto&& x = *it; |
||||
|
image[x.x + width*x.y] = q.backwards(x.c); |
||||
|
} |
||||
|
|
||||
|
in.clear(); |
||||
|
|
||||
|
// inverse wavelet transform in y-direction
|
||||
|
for(unsigned int i = 0; i < width; ++i){ |
||||
|
unwavelet(&image[i], height, width); |
||||
|
} |
||||
|
|
||||
|
// inverse wavelet transform in x-direction
|
||||
|
for(unsigned int i = 0; i < height; ++i){ |
||||
|
unwavelet(&image[i*width], width, 1); |
||||
|
} |
||||
|
|
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
int main(){ |
||||
|
namespace fs = boost::filesystem; |
||||
|
|
||||
|
fs::path directory("images"); |
||||
|
fs::directory_iterator eod; |
||||
|
for(fs::directory_iterator it(directory); it != eod; ++it){ |
||||
|
auto && path = it->path(); |
||||
|
if(path.extension() != ".png") continue; |
||||
|
|
||||
|
// open file
|
||||
|
std::string filename = path.string(); |
||||
|
std::cout << field("file") << filename << std::endl; |
||||
|
png::istream image(filename); |
||||
|
|
||||
|
auto width = image.get_width(); |
||||
|
auto height = image.get_height(); |
||||
|
|
||||
|
// read into vector
|
||||
|
std::vector<double> image_vec; |
||||
|
image_vec.reserve(width * height); |
||||
|
for(unsigned char c = 0; image >> c;) image_vec.push_back(c/255.0); |
||||
|
|
||||
|
// compress and decompress to see how we lost information
|
||||
|
unsigned int nzeros = 0; |
||||
|
auto compressed_vec = decompress(compress(image_vec, width, 0.1, nzeros)); |
||||
|
|
||||
|
// output some information
|
||||
|
std::cout << field("raw") << human_string(sizeof(uint8_t) * image_vec.size(), "b") << std::endl; |
||||
|
std::cout << field("compressed") << human_string(sizeof(jcmp::coefficient) * nzeros, "b") << std::endl; |
||||
|
|
||||
|
// save to output file
|
||||
|
std::string cfilename = "compressed/" + path.filename().string(); |
||||
|
png::gray_ostream compressed_image(width, height, cfilename); |
||||
|
for(unsigned int i = 0; i < compressed_vec.size(); ++i) compressed_image << compressed_vec[i]; |
||||
|
} |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include "jcmp_image.hpp" |
@ -0,0 +1,49 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
namespace jcmp { |
||||
|
// input in [0, 1], output [0, 255]
|
||||
|
inline uint8_t clamp_round(double x){ |
||||
|
if(x <= 0.0) return 0; |
||||
|
if(x >= 1.0) return 255; |
||||
|
return uint8_t(std::round(255.0*x)); |
||||
|
} |
||||
|
|
||||
|
// parameters which need to be stored in the file
|
||||
|
// to be able to dequantize
|
||||
|
struct quantize_params { |
||||
|
double f_max_abs; |
||||
|
double f_min_abs; |
||||
|
}; |
||||
|
|
||||
|
// Quantization may be non linear (given by f and f_inv)
|
||||
|
struct quantization { |
||||
|
std::function<double(double)> f; |
||||
|
std::function<double(double)> f_inv; |
||||
|
quantize_params p; |
||||
|
|
||||
|
quantization() = default; |
||||
|
|
||||
|
template <typename F> |
||||
|
quantization(F const & f_, F const & f_inv_, double max_abs, double min_abs) |
||||
|
: f(f_) |
||||
|
, f_inv(f_inv_) |
||||
|
, p{f(max_abs), f(min_abs)} |
||||
|
{} |
||||
|
|
||||
|
uint8_t forwards(double x){ |
||||
|
double y = std::abs(x); |
||||
|
y = (f(y) - p.f_min_abs) / (p.f_max_abs - p.f_min_abs); |
||||
|
if(x < 0) y = -y; |
||||
|
return clamp_round(0.5 * (y + 1.0)); |
||||
|
} |
||||
|
|
||||
|
double backwards(uint8_t x){ |
||||
|
double y = 2.0 * x / 255.0 - 1.0; |
||||
|
y = (p.f_max_abs - p.f_min_abs) * y; |
||||
|
if(y < 0) return -f_inv(-y + p.f_min_abs); |
||||
|
return f_inv(y + p.f_min_abs); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
static_assert(sizeof(quantize_params) == 16, "struct not propery packed"); |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
#include <includes.hpp> |
||||
|
#include <utilities.hpp> |
||||
|
|
||||
|
#include "jcmp_quantization.hpp" |
||||
|
|
||||
|
int main(){ |
||||
|
// no tests yet
|
||||
|
} |
@ -1,102 +1,3 @@ |
|||||
#pragma once |
#pragma once |
||||
|
|
||||
#include <includes.hpp> |
#include "wavelet_2.hpp" |
||||
|
|
||||
#include "striding_iterator.hpp" |
|
||||
#include "periodic_iterator.hpp" |
|
||||
#include "wavelet_constants.hpp" |
|
||||
|
|
||||
namespace wvlt { |
|
||||
namespace V1 { |
|
||||
// Apply the matrix Wn with the DAUB4 coefficients
|
|
||||
template <typename Iterator> |
|
||||
void wavelet_mul(Iterator begin, Iterator end){ |
|
||||
auto mul = end - begin; |
|
||||
std::vector<double> out(mul, 0.0); |
|
||||
for(int i = 0; i < mul; i += 2){ |
|
||||
out[i] = std::inner_product(evn_coef, evn_coef+4, periodic(begin, end) + i, 0.0); |
|
||||
out[i+1] = std::inner_product(odd_coef, odd_coef+4, periodic(begin, end) + i, 0.0); |
|
||||
} |
|
||||
for(int i = 0; i < mul; ++i){ |
|
||||
*begin++ = out[i]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Apply inverse of the matrix Wn with the DAUB4 coefficients
|
|
||||
template <typename Iterator> |
|
||||
void wavelet_inv(Iterator begin, Iterator end){ |
|
||||
auto mul = end - begin; |
|
||||
std::vector<double> out(mul, 0.0); |
|
||||
Iterator bc = begin; |
|
||||
for(int i = 0; i < mul; i += 2, begin += 2){ |
|
||||
Iterator b2 = begin + 1; |
|
||||
for(int j = 0; j < 4; ++j){ |
|
||||
out[(i+j) % mul] += *begin * evn_coef[j] + *b2 * odd_coef[j]; |
|
||||
} |
|
||||
} |
|
||||
for(int i = 0; i < mul; ++i){ |
|
||||
*bc++ = out[i]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Shuffle works with an extra copy, might be inefficient, but it is at least correct ;)
|
|
||||
// It applies the even-odd-sort matrix Sn
|
|
||||
template <typename Iterator> |
|
||||
void shuffle(Iterator begin, Iterator end){ |
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
|
||||
auto s = end - begin; |
|
||||
assert(s % 2 == 0); |
|
||||
|
|
||||
std::vector<value_type> v(s); |
|
||||
std::copy(strided(begin , 2), strided(end , 2), v.begin()); |
|
||||
std::copy(strided(begin+1, 2), strided(end+1, 2), v.begin() + s/2); |
|
||||
std::copy(v.begin(), v.end(), begin); |
|
||||
} |
|
||||
|
|
||||
template <typename Iterator> |
|
||||
void unshuffle(Iterator begin, Iterator end){ |
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
|
||||
auto s = end - begin; |
|
||||
assert(s % 2 == 0); |
|
||||
|
|
||||
std::vector<value_type> v(s); |
|
||||
std::copy(begin, begin + s/2, strided(v.begin(), 2)); |
|
||||
std::copy(begin + s/2, end, strided(v.begin()+1, 2)); |
|
||||
std::copy(v.begin(), v.end(), begin); |
|
||||
} |
|
||||
|
|
||||
// Combines the matrix Wn and Sn recusrively
|
|
||||
// Only works for inputs of size 2^m
|
|
||||
template <typename Iterator> |
|
||||
void wavelet(Iterator begin, Iterator end){ |
|
||||
auto s = end - begin; |
|
||||
assert(s >= 4); |
|
||||
for(int i = s; i >= 4; i >>= 1){ |
|
||||
// half interval
|
|
||||
end = begin + i; |
|
||||
assert(is_pow_of_two(end - begin)); |
|
||||
|
|
||||
// multiply with Wn
|
|
||||
wavelet_mul(begin, end); |
|
||||
// then with Sn
|
|
||||
shuffle(begin, end); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
template <typename Iterator> |
|
||||
void unwavelet(Iterator begin, Iterator end){ |
|
||||
auto s = end - begin; |
|
||||
assert(s >= 4); |
|
||||
for(int i = 4; i <= s; i <<= 1){ |
|
||||
// double interval
|
|
||||
end = begin + i; |
|
||||
assert(is_pow_of_two(end - begin)); |
|
||||
|
|
||||
// unshuffle: Sn^-1
|
|
||||
unshuffle(begin, end); |
|
||||
// then Wn^-1
|
|
||||
wavelet_inv(begin, end); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
@ -1,107 +0,0 @@ |
|||||
#include <includes.hpp> |
|
||||
|
|
||||
#include <boost/filesystem.hpp> |
|
||||
#include <png.hpp> |
|
||||
#include <utilities.hpp> |
|
||||
|
|
||||
#include "compressed_image.hpp" |
|
||||
#include "striding_iterator.hpp" |
|
||||
#include "periodic_iterator.hpp" |
|
||||
#include "wavelet.hpp" |
|
||||
|
|
||||
using namespace wvlt::V1; |
|
||||
|
|
||||
// note: we take a copy, because we will modify it in place
|
|
||||
jcmp::image compress(std::vector<double> image, int width, double threshold, int& zeros){ |
|
||||
auto height = image.size() / width; |
|
||||
assert(is_pow_of_two(width)); |
|
||||
assert(is_pow_of_two(height)); |
|
||||
|
|
||||
// wavelet transform in x-direction
|
|
||||
for(int i = 0; i < height; ++i){ |
|
||||
wavelet(image.begin() + i*width, image.begin() + (i+1)*width); |
|
||||
} |
|
||||
|
|
||||
// wavelet transform in y-direction
|
|
||||
for(int i = 0; i < width; ++i){ |
|
||||
wavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); |
|
||||
} |
|
||||
|
|
||||
// save the principal coefficients
|
|
||||
std::vector<jcmp::coefficient> v; |
|
||||
for(int y = 0; y < height; ++y){ |
|
||||
for(int x = 0; x < width; ++x){ |
|
||||
auto&& el = image[x + width*y]; |
|
||||
if(std::abs(el) > threshold) v.push_back({el, jcmp::uint(x), jcmp::uint(y)}); |
|
||||
else ++zeros; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return jcmp::image(width, height, std::move(v)); |
|
||||
} |
|
||||
|
|
||||
std::vector<double> decompress(jcmp::image in){ |
|
||||
auto width = in.header.width; |
|
||||
auto height = in.header.height; |
|
||||
assert(is_pow_of_two(width)); |
|
||||
assert(is_pow_of_two(height)); |
|
||||
|
|
||||
std::vector<double> image(width * height, 0.0); |
|
||||
|
|
||||
// read in coefficient on coordinates
|
|
||||
for(auto it = in.data.begin(); it != in.data.end(); ++it){ |
|
||||
auto&& x = *it; |
|
||||
image[x.x + width*x.y] = x.c; |
|
||||
} |
|
||||
|
|
||||
in.clear(); |
|
||||
|
|
||||
// inverse wavelet transform in y-direction
|
|
||||
for(int i = 0; i < width; ++i){ |
|
||||
unwavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); |
|
||||
} |
|
||||
|
|
||||
// inverse wavelet transform in x-direction
|
|
||||
for(int i = 0; i < height; ++i){ |
|
||||
unwavelet(image.begin() + i*width, image.begin() + (i+1)*width); |
|
||||
} |
|
||||
|
|
||||
return image; |
|
||||
} |
|
||||
|
|
||||
int main(){ |
|
||||
namespace fs = boost::filesystem; |
|
||||
|
|
||||
fs::path directory("images"); |
|
||||
fs::directory_iterator eod; |
|
||||
for(fs::directory_iterator it(directory); it != eod; ++it){ |
|
||||
auto && path = it->path(); |
|
||||
if(path.extension() != ".png") continue; |
|
||||
|
|
||||
// open file
|
|
||||
std::string filename = path.string(); |
|
||||
std::cout << field("file") << filename << std::endl; |
|
||||
png::istream image(filename); |
|
||||
|
|
||||
auto width = image.get_width(); |
|
||||
auto height = image.get_height(); |
|
||||
|
|
||||
// read into vector
|
|
||||
std::vector<double> image_vec; |
|
||||
image_vec.reserve(width * height); |
|
||||
for(unsigned char c = 0; image >> c;) image_vec.push_back(c/255.0); |
|
||||
|
|
||||
// compress and decompress to see how we lost information
|
|
||||
int zeros = 0; |
|
||||
auto compressed_vec = decompress(compress(image_vec, width, 0.5, zeros)); |
|
||||
|
|
||||
// output some information
|
|
||||
std::cout << field("raw") << human_string(image_vec.size()) << std::endl; |
|
||||
std::cout << field("compressed") << human_string(image_vec.size() - zeros) << std::endl; |
|
||||
|
|
||||
// save to output file
|
|
||||
std::string cfilename = "compressed/" + path.filename().string(); |
|
||||
png::gray_ostream compressed_image(width, height, cfilename); |
|
||||
for(int i = 0; i < compressed_vec.size(); ++i) compressed_image << compressed_vec[i]; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,102 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <includes.hpp> |
||||
|
|
||||
|
#include "striding_iterator.hpp" |
||||
|
#include "periodic_iterator.hpp" |
||||
|
#include "wavelet_constants.hpp" |
||||
|
|
||||
|
namespace wvlt { |
||||
|
namespace V1 { |
||||
|
// Apply the matrix Wn with the DAUB4 coefficients
|
||||
|
template <typename Iterator> |
||||
|
void wavelet_mul(Iterator begin, Iterator end){ |
||||
|
auto mul = end - begin; |
||||
|
std::vector<double> out(mul, 0.0); |
||||
|
for(int i = 0; i < mul; i += 2){ |
||||
|
out[i] = std::inner_product(evn_coef, evn_coef+4, periodic(begin, end) + i, 0.0); |
||||
|
out[i+1] = std::inner_product(odd_coef, odd_coef+4, periodic(begin, end) + i, 0.0); |
||||
|
} |
||||
|
for(int i = 0; i < mul; ++i){ |
||||
|
*begin++ = out[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Apply inverse of the matrix Wn with the DAUB4 coefficients
|
||||
|
template <typename Iterator> |
||||
|
void wavelet_inv(Iterator begin, Iterator end){ |
||||
|
auto mul = end - begin; |
||||
|
std::vector<double> out(mul, 0.0); |
||||
|
Iterator bc = begin; |
||||
|
for(int i = 0; i < mul; i += 2, begin += 2){ |
||||
|
Iterator b2 = begin + 1; |
||||
|
for(int j = 0; j < 4; ++j){ |
||||
|
out[(i+j) % mul] += *begin * evn_coef[j] + *b2 * odd_coef[j]; |
||||
|
} |
||||
|
} |
||||
|
for(int i = 0; i < mul; ++i){ |
||||
|
*bc++ = out[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Shuffle works with an extra copy, might be inefficient, but it is at least correct ;)
|
||||
|
// It applies the even-odd-sort matrix Sn
|
||||
|
template <typename Iterator> |
||||
|
void shuffle(Iterator begin, Iterator end){ |
||||
|
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
||||
|
auto s = end - begin; |
||||
|
assert(s % 2 == 0); |
||||
|
|
||||
|
std::vector<value_type> v(s); |
||||
|
std::copy(strided(begin , 2), strided(end , 2), v.begin()); |
||||
|
std::copy(strided(begin+1, 2), strided(end+1, 2), v.begin() + s/2); |
||||
|
std::copy(v.begin(), v.end(), begin); |
||||
|
} |
||||
|
|
||||
|
template <typename Iterator> |
||||
|
void unshuffle(Iterator begin, Iterator end){ |
||||
|
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
||||
|
auto s = end - begin; |
||||
|
assert(s % 2 == 0); |
||||
|
|
||||
|
std::vector<value_type> v(s); |
||||
|
std::copy(begin, begin + s/2, strided(v.begin(), 2)); |
||||
|
std::copy(begin + s/2, end, strided(v.begin()+1, 2)); |
||||
|
std::copy(v.begin(), v.end(), begin); |
||||
|
} |
||||
|
|
||||
|
// Combines the matrix Wn and Sn recusrively
|
||||
|
// Only works for inputs of size 2^m
|
||||
|
template <typename Iterator> |
||||
|
void wavelet(Iterator begin, Iterator end){ |
||||
|
auto s = end - begin; |
||||
|
assert(s >= 4); |
||||
|
for(int i = s; i >= 4; i >>= 1){ |
||||
|
// half interval
|
||||
|
end = begin + i; |
||||
|
assert(is_pow_of_two(end - begin)); |
||||
|
|
||||
|
// multiply with Wn
|
||||
|
wavelet_mul(begin, end); |
||||
|
// then with Sn
|
||||
|
shuffle(begin, end); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <typename Iterator> |
||||
|
void unwavelet(Iterator begin, Iterator end){ |
||||
|
auto s = end - begin; |
||||
|
assert(s >= 4); |
||||
|
for(int i = 4; i <= s; i <<= 1){ |
||||
|
// double interval
|
||||
|
end = begin + i; |
||||
|
assert(is_pow_of_two(end - begin)); |
||||
|
|
||||
|
// unshuffle: Sn^-1
|
||||
|
unshuffle(begin, end); |
||||
|
// then Wn^-1
|
||||
|
wavelet_inv(begin, end); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
Reference in new issue