summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-11-04 20:42:36 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2024-11-04 20:42:36 -0500
commit4bc851544831e37b6173d0ad05806fd841e216fb (patch)
treed6470fdb41fa6d9a87dd0a937c34d50af5529c35
parentb5cc6373fbfe2db20bb14216242c2c56f9bfbafe (diff)
downloadwizard-4bc851544831e37b6173d0ad05806fd841e216fb.tar.gz
wizard-4bc851544831e37b6173d0ad05806fd841e216fb.tar.bz2
wizard-4bc851544831e37b6173d0ad05806fd841e216fb.zip
Re-attempt 10 times, some tweaks to OCR, pre-filter card pool HEAD main
-rw-r--r--CMakeLists.txt2
-rw-r--r--cards_filter.rb41
-rw-r--r--cardset.cpp61
-rw-r--r--cardset.h6
-rw-r--r--wizard.cpp243
5 files changed, 202 insertions, 151 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 82e9d64..acc7ea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -17,6 +17,7 @@ include_directories(
17 vendor/asio/asio/include 17 vendor/asio/asio/include
18 vendor/base64/include 18 vendor/base64/include
19 ${tesseract_INCLUDE_DIRS} 19 ${tesseract_INCLUDE_DIRS}
20 #/Users/hatkirby/Repos/tesseract/include
20 ${lept_INCLUDE_DIRS} 21 ${lept_INCLUDE_DIRS}
21 ${GraphicsMagick_INCLUDE_DIRS} 22 ${GraphicsMagick_INCLUDE_DIRS}
22 ${curlcpp_INCLUDE_DIRS} 23 ${curlcpp_INCLUDE_DIRS}
@@ -26,6 +27,7 @@ include_directories(
26 27
27link_directories( 28link_directories(
28 ${tesseract_LIBRARY_DIRS} 29 ${tesseract_LIBRARY_DIRS}
30 #/Users/hatkirby/Repos/tesseract/build/here/usr/local/lib
29 ${lept_LIBRARY_DIRS} 31 ${lept_LIBRARY_DIRS}
30 ${GraphicsMagick_LIBRARY_DIRS} 32 ${GraphicsMagick_LIBRARY_DIRS}
31 ${curlcpp_LIBRARY_DIRS} 33 ${curlcpp_LIBRARY_DIRS}
diff --git a/cards_filter.rb b/cards_filter.rb new file mode 100644 index 0000000..aba66e3 --- /dev/null +++ b/cards_filter.rb
@@ -0,0 +1,41 @@
1require 'json'
2
3input = File.open(ARGV[0]) do |file|
4 JSON.load(file)
5end
6
7filtered = input.select do |cardJson|
8 cardJson["object"] == "card" &&
9 # It needs to have a downloadable image
10 cardJson.count("image_uris") &&
11 # Make sure we can support the card layout
12 ["normal", "leveler", "saga"].include?(cardJson["layout"]) &&
13 # We only support modern and m15 frames
14 ["2015", "2003"].include?(cardJson["frame"]) &&
15 # Digital cards look slightly different so ignore them
16 !cardJson["digital"] &&
17 # Only use english printings
18 cardJson["lang"] == "en" &&
19 # Currently not supporting silver bordered cards
20 cardJson["border_color"] != "silver" &&
21 # It is hard to read the name of a planeswalker
22 !cardJson["type_line"].include?("Planeswalker") &&
23 # This cuts out checklists and special tokens
24 cardJson["type_line"] != "Card" &&
25 # Amonkhet invocations are impossible
26 cardJson["set"] != "mp2" &&
27 # Unknown Event is not a real thing huh
28 cardJson["set"] != "da1"
29end.map do |cardJson|
30 {
31 "id" => cardJson["id"],
32 "name" => cardJson["name"],
33 "imageUri" => cardJson["image_uris"]["png"],
34 "frame" => cardJson["frame"],
35 "artist" => cardJson["artist"]
36 }
37end
38
39File.open(ARGV[1], 'w') do |file|
40 JSON.dump(filtered, file)
41end
diff --git a/cardset.cpp b/cardset.cpp index 4003eed..8097834 100644 --- a/cardset.cpp +++ b/cardset.cpp
@@ -14,53 +14,28 @@ cardset::cardset(std::string filename) {
14 nlohmann::json cardsJson = nlohmann::json::parse(contents.str()); 14 nlohmann::json cardsJson = nlohmann::json::parse(contents.str());
15 15
16 for (const auto& cardJson : cardsJson) { 16 for (const auto& cardJson : cardsJson) {
17 if ( 17 // We're pre-processing the cardset now to reduce the work done here (but
18 // The object needs to be a card 18 // mostly the space taken by the data).
19 cardJson["object"] == "card" && 19 card_frame frame;
20 // It needs to have a downloadable image 20
21 cardJson.count("image_uris") && 21 if (cardJson["frame"] == "2015") {
22 // Make sure we can support the card layout 22 frame = card_frame::m2015;
23 (cardJson["layout"] == "normal" || cardJson["layout"] == "leveler" || 23 } else if (cardJson["frame"] == "2003") {
24 cardJson["layout"] == "saga") && 24 frame = card_frame::modern;
25 // Digital cards look slightly different so ignore them 25 } else {
26 !cardJson["digital"] && 26 continue;
27 // Only use english printings 27 }
28 cardJson["lang"] == "en" &&
29 // Currently not supporting silver bordered cards
30 cardJson["border_color"] != "silver" &&
31 // It is hard to read the name of a planeswalker
32 cardJson["type_line"].get<std::string>().find("Planeswalker") ==
33 std::string::npos &&
34 // This cuts out checklists and special tokens
35 cardJson["type_line"] != "Card" &&
36 // Amonkhet invocations are impossible
37 cardJson["set"] != "mp2" &&
38 // Unknown Event is not a real thing huh
39 cardJson["set"] != "da1" &&
40 // Ignore cards with the special legendary flare
41 (!cardJson.count("frame_effects") ||
42 !cardJson["frame_effects"].count("legendary"))) {
43 card_frame frame;
44
45 if (cardJson["frame"] == "2015") {
46 frame = card_frame::m2015;
47 } else if (cardJson["frame"] == "2003") {
48 frame = card_frame::modern;
49 } else {
50 continue;
51 }
52 28
53 size_t cardId = cards_.size(); 29 size_t cardId = cards_.size();
54 cards_.emplace_back(cardId, cardJson["name"], 30 cards_.emplace_back(cardId, cardJson["name"], cardJson["imageUri"], frame,
55 cardJson["image_uris"]["png"], frame, cardJson["id"]); 31 cardJson["id"], cardJson["artist"]);
56 32
57 std::string canon = hatkirby::lowercase(cardJson["name"]); 33 std::string canon = hatkirby::lowercase(cardJson["name"]);
58 34
59 for (int i = 0; i < canon.length(); i++) { 35 for (int i = 0; i < canon.length(); i++) {
60 titles_.add(canon, {cardId, i}, i); 36 titles_.add(canon, {cardId, i}, i);
61 37
62 chars_.insert(canon.at(i)); 38 chars_.insert(canon.at(i));
63 }
64 } 39 }
65 } 40 }
66} 41}
diff --git a/cardset.h b/cardset.h index 217d859..2e39f5c 100644 --- a/cardset.h +++ b/cardset.h
@@ -17,14 +17,16 @@ struct card {
17 std::string imageUri; 17 std::string imageUri;
18 card_frame frame; 18 card_frame frame;
19 std::string uuid; 19 std::string uuid;
20 std::string artist;
20 21
21 card(size_t id, std::string name, std::string imageUri, card_frame frame, 22 card(size_t id, std::string name, std::string imageUri, card_frame frame,
22 std::string uuid) 23 std::string uuid, std::string artist)
23 : id(id), 24 : id(id),
24 name(std::move(name)), 25 name(std::move(name)),
25 imageUri(std::move(imageUri)), 26 imageUri(std::move(imageUri)),
26 frame(frame), 27 frame(frame),
27 uuid(std::move(uuid)) {} 28 uuid(std::move(uuid)),
29 artist(std::move(artist)) {}
28}; 30};
29 31
30class cardset { 32class cardset {
diff --git a/wizard.cpp b/wizard.cpp index a1d7df4..c99ec79 100644 --- a/wizard.cpp +++ b/wizard.cpp
@@ -55,6 +55,24 @@ void wizard::set_status_callback(status_callback_type callback) {
55} 55}
56 56
57Magick::Image wizard::run() { 57Magick::Image wizard::run() {
58 tesseract_ptr api{new tesseract::TessBaseAPI()};
59
60 if (api->Init(nullptr, "eng", tesseract::OEM_TESSERACT_LSTM_COMBINED)) {
61 throw std::runtime_error("Could not initialize tesseract");
62 }
63
64 std::string allChars;
65 for (char ch : cards_.getCharacters()) {
66 allChars.push_back(ch);
67 if (std::isalpha(ch)) {
68 allChars.push_back(std::toupper(ch));
69 }
70 }
71
72 if (!api->SetVariable("tessedit_char_whitelist", allChars.c_str())) {
73 throw std::runtime_error("Could not set tesseract allowlist");
74 }
75
58 std::string text = text_; //"what the heck, it's just some gay guy"; 76 std::string text = text_; //"what the heck, it's just some gay guy";
59 // getline(std::cin, text); 77 // getline(std::cin, text);
60 if (text.back() == '\r') { 78 if (text.back() == '\r') {
@@ -68,104 +86,109 @@ Magick::Image wizard::run() {
68 86
69 std::string canonText = hatkirby::lowercase(text); 87 std::string canonText = hatkirby::lowercase(text);
70 designer des(canonText, cards_.getTitles()); 88 designer des(canonText, cards_.getTitles());
71 std::list<usage> res = des.generate(rng_);
72 89
73 Magick::Image endImage; 90 Magick::Image endImage;
74 bool firstSlice = false; 91 bool success = false;
75 int ui = 0; 92 for (int attempt = 0; attempt < 10; attempt++) {
76 93 endImage = {};
77 for (const usage& u : res) { 94
78 const card& theCard = cards_.getCard(u.cardId); 95 std::list<usage> res = des.generate(rng_);
79 const std::string& cardName = theCard.name; 96
80 97 bool firstSlice = false;
81 std::cout << cardName.substr(0, u.strIndex) << "[" 98 int ui = 0;
82 << cardName.substr(u.strIndex, u.strLen) << "]" 99
83 << cardName.substr(u.strIndex + u.strLen) << std::endl; 100 bool failure = false;
84 101 for (const usage& u : res) {
85 std::cout << "Downloading image..." << std::endl; 102 const card& theCard = cards_.getCard(u.cardId);
86 if (status_callback_) { 103 const std::string& cardName = theCard.name;
87 status_callback_("Downloading image..."); 104
88 } 105 std::cout << cardName.substr(0, u.strIndex) << "["
89 106 << cardName.substr(u.strIndex, u.strLen) << "]"
90 Magick::Image cardImg = images_.get(theCard.uuid, theCard.imageUri); 107 << cardName.substr(u.strIndex + u.strLen) << std::endl;
91 108
92 std::cout << "Reading text..." << std::endl; 109 std::cout << "Downloading image..." << std::endl;
93 if (status_callback_) { 110 if (status_callback_) {
94 status_callback_("Reading text..."); 111 std::ostringstream status_stream;
95 } 112 status_stream << "Processing card " << (ui + 1) << "/" << res.size()
96 113 << " (attempt " << (attempt + 1) << ")...";
97 Magick::Image titleImg = cardImg; 114 status_callback_(status_stream.str());
98 titleImg.magick("TIFF"); 115 }
99
100 // titleImg.threshold(MaxRGB / 2);
101 titleImg.write("pre.tif");
102
103 Magick::Geometry margin;
104
105 if (theCard.frame == card_frame::m2015) {
106 margin = Magick::Geometry{595, 46, 57, 54};
107 } else if (theCard.frame == card_frame::modern) {
108 margin = Magick::Geometry{581, 50, 63, 57};
109 }
110
111 titleImg.crop(margin);
112 titleImg.zoom({margin.width() * 5, margin.height() * 5});
113
114 // titleImg.quantizeColorSpace(Magick::GRAYColorspace);
115 // titleImg.quantizeColors(2);
116 // titleImg.quantize();
117 titleImg.backgroundColor("white");
118 titleImg.matte(false);
119 titleImg.resolutionUnits(Magick::PixelsPerInchResolution);
120 titleImg.density({300, 300});
121 titleImg.type(Magick::GrayscaleType);
122 116
123 titleImg.write("title.tif"); 117 Magick::Image cardImg = images_.get(theCard.uuid, theCard.imageUri);
124 118
125 Magick::Blob titleBlob; 119 std::cout << "Reading text..." << std::endl;
126 titleImg.write(&titleBlob);
127 120
128 pix_ptr titlePix{ 121 Magick::Image titleImg = cardImg;
129 pixReadMemTiff(reinterpret_cast<const unsigned char*>(titleBlob.data()), 122 titleImg.magick("TIFF");
130 titleBlob.length(), 0)};
131 123
132 tesseract_ptr api{new tesseract::TessBaseAPI()}; 124 // titleImg.threshold(MaxRGB / 2);
125 titleImg.write("pre.tif");
133 126
134 if (api->Init(nullptr, "eng")) { 127 Magick::Geometry margin;
135 throw std::runtime_error("Could not initialize tesseract");
136 }
137 128
138 api->SetImage(titlePix.get()); 129 if (theCard.frame == card_frame::m2015) {
139 api->Recognize(nullptr); 130 margin = Magick::Geometry{595, 50, 57, 54};
131 } else if (theCard.frame == card_frame::modern) {
132 margin = Magick::Geometry{581, 50, 63, 57};
133 }
140 134
141 tesseract::ResultIterator* ri = api->GetIterator(); 135 titleImg.crop(margin);
142 tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE; 136 titleImg.zoom({margin.width() * 5, margin.height() * 5});
143 bool foundName = false; 137
144 size_t extraChars = 0; 138 // titleImg.quantizeColorSpace(Magick::GRAYColorspace);
139 // titleImg.quantizeColors(2);
140 // titleImg.quantize();
141 titleImg.backgroundColor("white");
142 titleImg.matte(false);
143 titleImg.resolutionUnits(Magick::PixelsPerInchResolution);
144 titleImg.density({300, 300});
145 titleImg.type(Magick::GrayscaleType);
146 titleImg.gamma(3);
147 // titleImg.level(0.2, 0.8);
148
149 titleImg.write("title.tif");
150
151 Magick::Blob titleBlob;
152 titleImg.write(&titleBlob);
153
154 pix_ptr titlePix{pixReadMemTiff(
155 reinterpret_cast<const unsigned char*>(titleBlob.data()),
156 titleBlob.length(), 0)};
157
158 api->SetImage(titlePix.get());
159 api->Recognize(nullptr);
160
161 tesseract::ResultIterator* ri = api->GetIterator();
162 tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE;
163 bool foundName = false;
164 size_t extraChars = 0;
165
166 if (ri) {
167 do {
168 const char* line = ri->GetUTF8Text(level);
169
170 if (line) {
171 std::string lineStr(line);
172
173 size_t leadin = hatkirby::lowercase(lineStr).find(
174 hatkirby::lowercase((cardName)));
175 if (leadin != std::string::npos) {
176 foundName = true;
177 extraChars = leadin;
178
179 break;
180 } /* else {
181 std::cout << "WRONG: " << lineStr << std::endl;
182 }*/
183 }
184 } while (ri->Next(level));
185 }
145 186
146 if (ri) { 187 if (!foundName) {
147 do { 188 failure = true;
148 const char* line = ri->GetUTF8Text(level); 189 break;
149 190 }
150 if (line) {
151 std::string lineStr(line);
152
153 size_t leadin = hatkirby::lowercase(lineStr).find(
154 hatkirby::lowercase((cardName)));
155 // if (leadin != std::string::npos)
156 {
157 foundName = true;
158 // extraChars = leadin;
159
160 break;
161 } /* else {
162 std::cout << "WRONG: " << lineStr << std::endl;
163 }*/
164 }
165 } while (ri->Next(level));
166 }
167 191
168 if (foundName) {
169 level = tesseract::RIL_SYMBOL; 192 level = tesseract::RIL_SYMBOL;
170 193
171 for (int i = 0; i < extraChars; i++) { 194 for (int i = 0; i < extraChars; i++) {
@@ -233,32 +256,40 @@ Magick::Image wizard::run() {
233 256
234 cardImg.magick("PNG"); 257 cardImg.magick("PNG");
235 cardImg.write("slice.png"); 258 cardImg.write("slice.png");
236 } else {
237 std::cout << "Didn't find name" << std::endl;
238 }
239 259
240 if (!firstSlice) { 260 if (!firstSlice) {
241 firstSlice = true; 261 firstSlice = true;
242 endImage = cardImg; 262 endImage = cardImg;
243 263
244 if (theCard.frame == card_frame::m2015) { 264 if (theCard.frame == card_frame::m2015) {
245 // endImage.extent({endImage.columns(), endImage.rows() + 6}, 265 // endImage.extent({endImage.columns(), endImage.rows() + 6},
246 // Magick::SouthGravity); 266 // Magick::SouthGravity);
247 } 267 }
248 } else { 268 } else {
249 int xoff = endImage.columns(); 269 int xoff = endImage.columns();
250 270
251 endImage.backgroundColor("black"); 271 endImage.backgroundColor("black");
252 272
253 endImage.extent({endImage.columns() + cardImg.columns(), cardImg.rows()}, 273 endImage.extent(
254 Magick::WestGravity); 274 {endImage.columns() + cardImg.columns(), cardImg.rows()},
275 Magick::WestGravity);
255 276
256 endImage.composite(cardImg, xoff, 277 endImage.composite(cardImg, xoff,
257 (theCard.frame == card_frame::m2015) ? 6 : 0); 278 (theCard.frame == card_frame::m2015) ? 6 : 0);
279 }
280
281 // break;
282 ui++;
258 } 283 }
259 284
260 // break; 285 if (!failure) {
261 ui++; 286 success = true;
287 break;
288 }
289 }
290
291 if (!success) {
292 throw std::domain_error("Could not generate card");
262 } 293 }
263 294
264 endImage.magick("PNG"); 295 endImage.magick("PNG");