The pageview counter designed for this site, i.e., the one at the footer. I estimate that 99% of people nowadays use Python when dealing with MNIST. But this site promises not to use Python, thus chooses C++ for this gadget. The overall experience is not much complicated than Python, while it is expected to save a lot of carbons😁.

Implementation

Download MNIST testset and uncompress by gzip. LeCun said that the first 5k images are easier, so the program only use the first 5k images. Counter is saved into a file fcounter.db. Digits are randomly chosen from test images, assembled into one PNG image, (with Magick++ which is easier), then returned to the webserver via FastCGI.

Code
counter.cppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <fstream>
#include <cstring>
#include <random>
#include <map>
#include <Magick++.h>
#include <fcgio.h>

std::ifstream t10k_images("./t10k-images-idx3-ubyte", std::ios_base::binary | std::ios_base::in);
std::ifstream t10k_labels("./t10k-labels-idx1-ubyte", std::ios_base::binary | std::ios_base::in);
size_t count = 0;

enum {
IMAGE_IN = 16,
IMAGE_NUM = 5000,
IMAGE_SIZE = 28,
IMAGE_BYTE = IMAGE_SIZE * IMAGE_SIZE,
LABEL_IN = 8,
};

std::multimap<int, size_t> categories;
std::random_device rd;
std::mt19937 mt(rd());

void init() {
t10k_labels.seekg(LABEL_IN);
for (size_t i = 0; i < IMAGE_NUM; i++) {
unsigned char c;
t10k_labels.read(reinterpret_cast<char*>(&c), 1);
categories.insert({c, i * IMAGE_BYTE + IMAGE_IN});
}
std::ifstream fcounter("./fcounter.db", std::ios_base::binary | std::ios_base::in);
fcounter.read(reinterpret_cast<char*>(&count), sizeof(count));
fcounter.close();
}

void select(std::array<unsigned char, IMAGE_BYTE>& img, unsigned char c) {
auto range = categories.equal_range(c);
auto first = range.first; auto last = range.second;
auto n = std::distance(first, last);
std::uniform_int_distribution<> dist(0, n - 1);
auto sk = std::next(first, dist(mt))->second;
t10k_images.seekg(sk);
t10k_images.read(reinterpret_cast<char*>(img.data()), IMAGE_BYTE);
}

void hit(std::ostream& os) {
count++;
std::ofstream fcounter("./fcounter.db", std::ios_base::binary | std::ios_base::out);
fcounter.write(reinterpret_cast<char*>(&count), sizeof(count));
fcounter.close();
std::string str = std::to_string(count);
if (str.length() < 6)
str = std::string(6 - str.length(), '0') + str;
size_t w = IMAGE_SIZE * str.length(), h = IMAGE_SIZE;
std::vector<unsigned char> canvas(w*h, 0);
size_t i = 0;
for (auto&& c : str) {
std::array<unsigned char, IMAGE_BYTE> img;
select(img, c - '0');
for (int y = 0; y < IMAGE_SIZE; y++) {
std::memcpy(&canvas[y * w + i * IMAGE_SIZE], &img[y * IMAGE_SIZE], IMAGE_SIZE);
}
i++;
}
Magick::Image image(IMAGE_SIZE*str.length(), IMAGE_SIZE, "I", Magick::CharPixel, canvas.data());
Magick::Blob blob;
image.type(Magick::GrayscaleType);
image.magick("PNG");
image.write(&blob);
os << "Content-Type: image/png\r\n";
os << "Content-length: " << blob.length() << "\r\n\r\n";
os.write(reinterpret_cast<const char*>(blob.data()), blob.length()) << std::flush;
}

int main() {
FCGX_Request request;
init();
FCGX_Init();
FCGX_InitRequest(&request, 0, 0);
while (FCGX_Accept_r(&request) == 0) {
fcgi_streambuf osbuf(request.out);
std::ostream os(&osbuf);
hit(os);
}
return 0;
}

The code is put in public domain now.

Compilation

  • Install APT packages libmagick++-dev libfcgi-dev.
  • For FastCGI++, compiler flags -lfcgi++ -lfcgi are required.
  • CMake can found Magick++ automatically. Otherwise, append compiler flags from magick++-config.

Deployment

I use spawn-fcgi to spawn the compiled binary (set as a service in systemd). Most webservers support FastCGI, a reverse proxy to the port set by spawn-fcgi completes the deployment. I use Caddy:

1
2
3
reverse_proxy /counter.png localhost:21930 {
transport fastcgi
}

Add /counter.png to the footer HTML, then you see digits bump on refresh. Go through internal links will not bump if the explorer cached the image.