├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Test Plan.jmx ├── database ├── LICENSE.md ├── README.md ├── backup_wm_raw_draw_data.py ├── big-picture-database.png ├── clean_database.py ├── complete-dump │ ├── 20080912003-1.sql.gz │ └── single-tables │ │ ├── wm_formula.sql │ │ ├── wm_formula2challenge.sql │ │ ├── wm_formula_svg_missing.sql │ │ ├── wm_invalid_formula_requests.sql │ │ ├── wm_languages.sql │ │ ├── wm_raw_data2formula.sql │ │ ├── wm_renderings.sql │ │ ├── wm_similarity.sql │ │ ├── wm_tags.sql │ │ ├── wm_tags2symbols.sql │ │ ├── wm_user_unknown_formula.sql │ │ ├── wm_users.sql │ │ └── wm_workers.sql ├── import_database.py └── structure │ ├── foreign-keys.sql │ └── write-math.sql ├── detexify-import └── importer.py ├── index.php ├── latex-unicode ├── read.py └── unicode.xml ├── tools ├── README.md ├── backup.py ├── cleanup │ ├── create_testset_online_once.py │ └── normalize_formulas_online.py ├── db_dump.py ├── distance_metric.py ├── dtw_classifier.py ├── evaluate_preprocessing_algorithms.py ├── find_errors.py ├── find_outliers.py ├── find_wrong_segmentation.py ├── find_wrong_symbol_count.py ├── get_probabilites_of_stroke_counts.py ├── is_one_symbol_classifier.pickle ├── language_model │ ├── find_mathmode.py │ ├── get_vocabulary.py │ ├── language-model-building │ │ ├── LatexMathSymbolCounter │ │ │ ├── LatexMathSymbolCounter.sln │ │ │ ├── LatexMathSymbolCounter.v12.suo │ │ │ └── LatexMathSymbolCounter │ │ │ │ ├── App.config │ │ │ │ ├── LatexMathSymbolCounter.csproj │ │ │ │ ├── LatexMathSymbolCounter.csproj.user │ │ │ │ ├── LatexReader.cs │ │ │ │ ├── Program.cs │ │ │ │ ├── Properties │ │ │ │ └── AssemblyInfo.cs │ │ │ │ └── obj │ │ │ │ └── Debug │ │ │ │ ├── DesignTimeResolveAssemblyReferencesInput.cache │ │ │ │ ├── LatexMathSymbolCounter.csproj.FileListAbsolute.txt │ │ │ │ ├── TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs │ │ │ │ ├── TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs │ │ │ │ ├── TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs │ │ │ │ ├── lmsc.exe │ │ │ │ └── lmsc.pdb │ │ ├── README.md │ │ ├── build-language-model.py │ │ ├── latex-parser.py │ │ ├── parse_folder.py │ │ └── parse_wikipedia.py │ ├── parse_arxiv.py │ └── parse_mathmode.py ├── model2json.py ├── symbol-generator │ ├── Makefile │ ├── README.md │ ├── generate.py │ ├── manual │ │ ├── Makefile │ │ ├── cavalieri-quadrature-formula.png │ │ ├── formula.template.tex │ │ ├── hesse-matrix.png │ │ ├── render.py │ │ └── symbol.tex │ ├── symbol.tex │ └── symbols.yaml ├── symbol-lists │ └── tiny.yml ├── sync_answer_counts.py ├── templates │ ├── base.twig │ └── classification-error-report.html └── write_math_utils.py ├── website ├── README.md ├── about │ └── index.php ├── admin-svg-problems │ └── index.php ├── admin │ ├── index.php │ └── phraselist.php ├── api │ ├── api.functions.php │ ├── get_unclassified.php │ ├── index.php │ ├── save-data.php │ ├── set-accept-partial.php │ ├── set-segmentation.php │ ├── set-unaccept-partial.php │ └── submit-partial-answer.php ├── classification.php ├── classify │ ├── index.php │ └── svg-template.svg ├── compare │ └── index.php ├── composer.json ├── config.template.php ├── css │ └── style.css ├── data │ ├── 2015-01-28-data │ │ ├── hist.py │ │ └── symbols.csv │ ├── index.html │ ├── odbl-10.txt │ └── style.css ├── favicon.ico ├── feature_extraction.php ├── fonts │ ├── STIXFontLicense2010.txt │ ├── STIXGeneral.woff │ ├── STIXNonUni.woff │ └── STIXNonUniIta.woff ├── forgot │ └── index.php ├── formulas │ ├── .htaccess │ └── cache-404.php ├── gallery │ └── index.php ├── icon-256.png ├── icon-64.png ├── icon.svg ├── icons │ ├── Crystal_Clear_action_apply-gray.png │ ├── Crystal_Clear_action_apply.png │ ├── accept.png │ ├── cross.png │ ├── offline.svg │ └── pencil.png ├── index.php ├── init.php ├── js │ ├── accept_partial.js │ ├── segmentation.js │ ├── submit_partial.js │ ├── symbolstrokes.js │ └── unaccept_partial.js ├── latex.php ├── login │ └── index.php ├── maintenance.html ├── manifest.json ├── nice-highres.png ├── niceicon.png ├── preprocessing.php ├── problems │ └── index.php ├── profile │ └── index.php ├── ranking │ └── index.php ├── raw-data │ ├── .htaccess │ └── cache-404.php ├── receive.php ├── register │ └── index.php ├── render │ ├── functions.php │ └── index.php ├── search │ └── index.php ├── segmentation.php ├── segmentation │ └── 294065.svg ├── session.php ├── similarity │ └── index.php ├── stats │ └── index.php ├── svg.php ├── symbol-identification │ └── index.php ├── symbol │ ├── index.php │ └── symbol.functions.php ├── tags │ ├── .htaccess │ └── index.php ├── templates │ ├── about.twig │ ├── admin.twig │ ├── base.twig │ ├── classify.twig │ ├── compare.twig │ ├── forgot.twig │ ├── gallery.twig │ ├── login.twig │ ├── models.twig │ ├── pagination.twig │ ├── problems.twig │ ├── profile.twig │ ├── ranking.twig │ ├── recordingjs.twig │ ├── register.twig │ ├── render.twig │ ├── search.twig │ ├── similarity.twig │ ├── stats.twig │ ├── svg-problems.twig │ ├── symbol-identification.twig │ ├── symbol.twig │ ├── tag.twig │ ├── tags_overview.twig │ ├── train.twig │ ├── unicode.twig │ ├── user.twig │ ├── view.twig │ └── worker.twig ├── train │ ├── functions.php │ └── index.php ├── unicode │ ├── README.md │ ├── create_json.py │ ├── index.php │ └── unicode.json ├── user.func.php ├── user │ └── index.php ├── view │ ├── functions.php │ ├── index.php │ └── submit_answer.php ├── worker │ └── index.php ├── write-math.sublime-project └── writemath.appcache └── writing-examples ├── README.md ├── easel-js └── index.html ├── fabric-js └── index.html ├── paper-js └── index.html └── simple-pure-js ├── index.html └── recognition.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: F5Eg0QP5zdnJWBd6u4aAGGSvelUFbzWhW -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # Project specific 39 | papers 40 | latex-unicode/dbconfig.py 41 | tools/symbol-generator/dbconfig.py 42 | tools/symbol-generator/tmp.tex 43 | tools/symbol-generator/tmp.log 44 | tools/symbol-generator/tmp.aux 45 | tools/symbol-generator/*.pdf 46 | tools/symbol-generator/*.svg 47 | tools/symbol-generator/manual/*.pdf 48 | tools/symbol-generator/manual/*.svg 49 | website/config.php 50 | website/cache/* 51 | website/composer.lock 52 | website/vendor/* 53 | website/write-math.sublime-workspace 54 | 55 | # Both of these folders have to be writable 56 | website/formulas/*.svg 57 | website/raw-data 58 | 59 | 60 | # LaTex 61 | *.aux 62 | *.log 63 | *.toc 64 | *.out 65 | *.blg 66 | *.bbl 67 | *.glg 68 | *.glo 69 | *.ist 70 | *.gls 71 | *.xdy 72 | 73 | *config.py 74 | *config.yaml 75 | *config.yml 76 | *config.php 77 | *.bak 78 | *.sublime-workspace 79 | 80 | website/clients/nn-python/experiments/ 81 | private/ 82 | wm_raw_draw_data_*.sql 83 | detexify-import/done/ 84 | detexify-import/skipped/ 85 | detexify-import/splitted/ 86 | tmp/ 87 | project.yml 88 | *.html 89 | testresult_* 90 | DO.* 91 | Makefile -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | # command to install dependencies 5 | install: "pip install natsort" 6 | script: 7 | - cd tools 8 | after_success: 9 | - coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Martin Thoma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/MartinThoma/write-math.svg?branch=master)](https://travis-ci.org/MartinThoma/write-math) 2 | [![Coverage Status](https://coveralls.io/repos/MartinThoma/write-math/badge.png)](https://coveralls.io/r/MartinThoma/write-math) 3 | [![Code Issues](http://www.quantifiedcode.com/api/v1/project/dee640dc587341a9ad8a05e6ae4471e1/badge.svg)](http://www.quantifiedcode.com/app/project/dee640dc587341a9ad8a05e6ae4471e1) 4 | 5 | write-math 6 | ========== 7 | 8 | On-line recognition of mathematical formulae. 9 | 10 | ## Technology 11 | Client-Side: 12 | * HTML 13 | * CSS 14 | * JavaScript 15 | * fabric.js (see [example](http://fabricjs.com/freedrawing/), [tutorial](http://fabricjs.com/fabric-intro-part-4/)) 16 | * [mathjax](http://www.mathjax.org/) for rendering LaTeX 17 | 18 | Server-Side: 19 | * Currently PHP+Python, but I'm thinking about switching (Haskell, Scala, Python, PHP, Java, C, C++?) 20 | 21 | 22 | ## TODO 23 | 24 | http://scikit-learn.org/stable/modules/hmm.html 25 | 26 | webdemo.myscript.com - seems to be pretty good! 27 | 28 | 29 | how can I get equidistant points on a curve 30 | * http://www.mathworks.com/matlabcentral/newsreader/view_thread/317568 31 | * http://www.mathworks.com/matlabcentral/fileexchange/34874-interparc 32 | 33 | ## Important Misc 34 | 35 | * [IAPR-TC11:Reading Systems](http://www.iapr-tc11.org/mediawiki/index.php/IAPR-TC11:Reading_Systems) 36 | * [Datasets](http://www.iapr-tc11.org/mediawiki/index.php/Datasets) 37 | * Journals: 38 | * International Journal on Document Analysis and Recognition (IJDAR) 39 | * Conferences: 40 | * ICDAR (International Conference on Document Analysis and Recognition) 41 | * IWFHR (International Workshop on Frontiers in Handwriting Recognition) 42 | * http://www.isical.ac.in/~crohme/ 43 | 44 | 45 | 46 | * http://saskatoon.cs.rit.edu/inkml_viewer/ 47 | * http://www.isical.ac.in/~crohme/CROHME_data.html 48 | * http://www.isical.ac.in/~crohme/data2.html 49 | 50 | 51 | ## Regular Cleanup / updates 52 | 53 | To make sure that the user experience is good, execute the following scripts 54 | on a regular basis: 55 | 56 | * `tools/create_testset_online_once.py`: It adds new recordings to the test set 57 | - make sure that they are correct! -------------------------------------------------------------------------------- /database/LICENSE.md: -------------------------------------------------------------------------------- 1 | The database is licensed under the [ODbL](odbl-10.txt). This license is also 2 | used by [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Open_Database_License). 3 | A human-readable form of the license can be found at http://opendatacommons.org/licenses/odbl/summary/. 4 | 5 | Feel free to contact [me](info@martin-thoma.de) if you have questions. 6 | 7 | The major part of the data is from [detexify-data](https://github.com/kirel/detexify-data). -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | I will create irregular database dumps. If you think I should update the 2 | dump, just send me an email (info@martin-thoma.de). 3 | 4 | ![A visual overview over the database](https://raw.githubusercontent.com/MartinThoma/write-math/master/database/big-picture-database.png) 5 | 6 | The main part of the database, the `wm_raw_data`, can be backed up within 40s 7 | (2014-07-21) from the server. That can be done with `backup_wm_raw_draw_data.py`. 8 | 9 | Adding all data from the sql textfiles to the MySQL database takes much longer: 10 | 11 | ```bash 12 | $ time ./import_database.py 13 | Import schema 14 | Import Table 'wm_formula.sql'... done in 0.33 s 15 | Import Table 'wm_formula_svg_missing.sql'... done in 0.11 s 16 | Import Table 'wm_invalid_formula_requests.sql'... done in 0.08 s 17 | Import Table 'wm_languages.sql'... done in 0.08 s 18 | Import Table 'wm_raw_draw_data_1.sql'... done in 782.53 s 19 | 20 | ``` 21 | 22 | ## License 23 | 24 | The database is licensed under the [ODbL](odbl-10.txt). This license is also 25 | used by [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Open_Database_License). 26 | A human-readable form of the license can be found at http://opendatacommons.org/licenses/odbl/summary/. 27 | 28 | Feel free to contact [me](info@martin-thoma.de) if you have questions. 29 | 30 | ## Credits 31 | 32 | The major part of the data is from [detexify-data](https://github.com/kirel/detexify-data). 33 | It was included into the dataset by the user "Detexify". 34 | 35 | ## raw draw data 36 | 37 | The most important part - the `wm_raw_draw_data` table - is too big to put it 38 | under version control. 39 | 40 | This data can be downloaded via [www.dropbox.com](https://www.dropbox.com/sh/rov7qyxi1c00dmi/AADLbZitMVLTQVY1D89tif8pa) 41 | 42 | ## Importing 43 | 44 | * Download this repository 45 | * Download all `wm_raw_draw_data` files and put them into 46 | `database/complete-dump/single-tables` (should be at least 24) 47 | * adjust the `/var/www/write-math/website/clients/python/db.config.yml` 48 | * create a `write-math` MySQL database 49 | * `./clean_database.py`: About 5 seconds 50 | * `./import_database.py`: 5-6 hours -------------------------------------------------------------------------------- /database/backup_wm_raw_draw_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """This script can be used for creating a backup of wm_raw_draw_data.""" 5 | 6 | from __future__ import print_function 7 | import pymysql 8 | import pymysql.cursors 9 | from dbconfig import mysql_online 10 | import time 11 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 12 | 13 | 14 | def main(mysql, start): 15 | """ 16 | Download wm_raw_draw_data and store it in text files with 2000 inserts 17 | each. Those files are stored in the same folder as this script is in. 18 | """ 19 | pagesize = 2000 20 | print("Connect to database") 21 | connection = pymysql.connect(host=mysql['host'], 22 | user=mysql['user'], 23 | passwd=mysql['passwd'], 24 | db=mysql['db'], 25 | cursorclass=pymysql.cursors.DictCursor) 26 | cursor = connection.cursor() 27 | print("Get counter") 28 | sql = "SELECT COUNT( * ) AS counter FROM `wm_raw_draw_data`" 29 | cursor.execute(sql) 30 | count = cursor.fetchone()['counter'] 31 | print("Counter: %i" % count) 32 | 33 | datacounter = 0 34 | filecounter = 1 35 | f = open("wm_raw_draw_data_%i.sql" % filecounter, "w") 36 | 37 | # Speed up insertion 38 | f.write("LOCK TABLES wm_raw_draw_data WRITE;") 39 | 40 | for i in range(start, count, pagesize): 41 | start_time = time.time() 42 | sql = "SELECT * FROM `wm_raw_draw_data` LIMIT %i, %i" % (i, pagesize) 43 | cursor.execute(sql) 44 | datasets = cursor.fetchall() 45 | for data in datasets: 46 | keys = data.keys() 47 | keys_str = ",".join(map(lambda n: "`%s`" % str(n), keys)) 48 | values = [data[key] for key in keys] 49 | values = ",".join(map(lambda n: "'%s'" % str(n), values)) 50 | # IGNORE makes it possible to easily restart insertion 51 | sql = ("INSERT IGNORE INTO `wm_raw_draw_data` " 52 | "(%s) VALUES (%s);\n" % (keys_str, values)) 53 | f.write(sql) 54 | datacounter += 1 55 | if datacounter % 10000 == 0: 56 | filecounter += 1 57 | f.write("UNLOCK TABLES;") 58 | f.close() 59 | f = open("wm_raw_draw_data_%i.sql" % filecounter, "w") 60 | elapsed_time = time.time() - start_time 61 | print("Downloaded %i datasets. Last %i in %0.2f seconds" % 62 | (datacounter, pagesize, elapsed_time)) 63 | f.write("UNLOCK TABLES;") 64 | f.close() 65 | if __name__ == '__main__': 66 | parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) 67 | parser.add_argument("-s", "--start", type=int, 68 | default=0, dest="start", 69 | help="how many IDs should be skipped?") 70 | args = parser.parse_args() 71 | main(mysql_online, args.start) 72 | -------------------------------------------------------------------------------- /database/big-picture-database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/database/big-picture-database.png -------------------------------------------------------------------------------- /database/clean_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Drop all database tables.""" 4 | 5 | from __future__ import print_function 6 | import pymysql 7 | import pymysql.cursors 8 | import sys 9 | import yaml 10 | 11 | 12 | def query_yes_no(question, default="yes"): 13 | """Ask a yes/no question via raw_input() and return their answer. 14 | 15 | "question" is a string that is presented to the user. 16 | "default" is the presumed answer if the user just hits . 17 | It must be "yes" (the default), "no" or None (meaning 18 | an answer is required of the user). 19 | 20 | The "answer" return value is one of "yes" or "no". 21 | """ 22 | valid = {"yes": True, "y": True, "ye": True, 23 | "no": False, "n": False} 24 | if default is None: 25 | prompt = " [y/n] " 26 | elif default == "yes": 27 | prompt = " [Y/n] " 28 | elif default == "no": 29 | prompt = " [y/N] " 30 | else: 31 | raise ValueError("invalid default answer: '%s'" % default) 32 | 33 | while True: 34 | sys.stdout.write(question + prompt) 35 | choice = raw_input().lower() 36 | if default is not None and choice == '': 37 | return valid[default] 38 | elif choice in valid: 39 | return valid[choice] 40 | else: 41 | sys.stdout.write("Please respond with 'yes' or 'no' " 42 | "(or 'y' or 'n').\n") 43 | 44 | 45 | def clean(mysql): 46 | """Drop all tables from the database. """ 47 | connection = pymysql.connect(host="localhost", # mysql['host'], 48 | user=mysql['user'], 49 | passwd=mysql['passwd'], 50 | db=mysql['db'], 51 | cursorclass=pymysql.cursors.DictCursor) 52 | cursor = connection.cursor() 53 | 54 | sql = ("SET foreign_key_checks = 0;DROP TABLE " 55 | "`wm_dtw_worker_data`, `wm_flags`, `wm_formula`, " 56 | "`wm_formula2challenge`, `wm_formula_in_paper`, " 57 | "`wm_formula_svg_missing`, `wm_invalid_formula_requests`, " 58 | "`wm_languages`, `wm_papers`, " 59 | "`wm_raw_draw_data`, `wm_renderings`, `wm_similarity`, " 60 | "`wm_users`, `wm_user_unknown_formula`, " 61 | "`wm_workers`, `wm_worker_answers`;SET foreign_key_checks = 1;") 62 | a = cursor.execute(sql) 63 | print(a) 64 | 65 | if __name__ == '__main__': 66 | yamlfile = "/var/www/write-math/website/clients/python/db.config.yml" 67 | with open(yamlfile, 'r') as ymlfile: 68 | cfg = yaml.load(ymlfile) 69 | if query_yes_no("Do you want to remove all content from the database? " 70 | "This step cannot be undone!", "no"): 71 | clean(cfg['mysql_local']) 72 | -------------------------------------------------------------------------------- /database/complete-dump/20080912003-1.sql.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/database/complete-dump/20080912003-1.sql.gz -------------------------------------------------------------------------------- /database/complete-dump/single-tables/wm_formula2challenge.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.5.4 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 134.0.30.203:3306 6 | -- Erstellungszeit: 17. Jul 2014 um 19:03 7 | -- Server Version: 5.5.28a-MariaDB 8 | -- PHP-Version: 5.3.19 9 | 10 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | 19 | -- 20 | -- Datenbank: `20080912003-1` 21 | -- 22 | 23 | -- 24 | -- Daten für Tabelle `wm_formula2challenge` 25 | -- 26 | 27 | INSERT INTO `wm_formula2challenge` (`id`, `challenge_id`, `formula_id`) VALUES 28 | (1, 1, 31), 29 | (2, 1, 32), 30 | (3, 1, 33), 31 | (4, 1, 34), 32 | (5, 1, 35), 33 | (6, 1, 36), 34 | (7, 1, 37), 35 | (8, 1, 38), 36 | (9, 1, 39), 37 | (10, 1, 40), 38 | (11, 1, 41), 39 | (12, 1, 42), 40 | (13, 1, 43), 41 | (14, 1, 44), 42 | (15, 1, 45), 43 | (16, 1, 46), 44 | (26, 1, 47), 45 | (17, 1, 48), 46 | (18, 1, 49), 47 | (19, 1, 50), 48 | (20, 1, 51), 49 | (21, 1, 52), 50 | (22, 1, 53), 51 | (23, 1, 54), 52 | (24, 1, 55), 53 | (25, 1, 56), 54 | (27, 5, 70), 55 | (133, 9, 70), 56 | (28, 5, 71), 57 | (128, 9, 71), 58 | (29, 5, 72), 59 | (129, 9, 72), 60 | (30, 5, 73), 61 | (31, 5, 74), 62 | (32, 5, 75), 63 | (33, 5, 76), 64 | (34, 5, 77), 65 | (35, 5, 78), 66 | (36, 5, 79), 67 | (102, 4, 81), 68 | (122, 9, 81), 69 | (63, 4, 82), 70 | (119, 9, 82), 71 | (65, 4, 87), 72 | (121, 9, 87), 73 | (101, 4, 89), 74 | (37, 3, 90), 75 | (38, 3, 91), 76 | (39, 3, 92), 77 | (40, 3, 93), 78 | (41, 3, 94), 79 | (42, 3, 95), 80 | (132, 9, 95), 81 | (43, 3, 96), 82 | (44, 3, 97), 83 | (45, 3, 98), 84 | (46, 3, 99), 85 | (47, 3, 100), 86 | (48, 3, 101), 87 | (49, 3, 102), 88 | (50, 3, 103), 89 | (51, 3, 104), 90 | (52, 3, 105), 91 | (53, 3, 106), 92 | (54, 3, 107), 93 | (55, 3, 108), 94 | (56, 3, 109), 95 | (57, 3, 110), 96 | (59, 3, 111), 97 | (60, 3, 112), 98 | (61, 3, 113), 99 | (123, 9, 113), 100 | (62, 3, 114), 101 | (58, 3, 115), 102 | (66, 4, 117), 103 | (67, 4, 150), 104 | (68, 4, 151), 105 | (131, 9, 151), 106 | (69, 4, 152), 107 | (127, 9, 152), 108 | (70, 4, 153), 109 | (71, 4, 154), 110 | (72, 4, 155), 111 | (73, 4, 156), 112 | (74, 4, 157), 113 | (75, 4, 158), 114 | (76, 4, 159), 115 | (77, 4, 160), 116 | (78, 4, 161), 117 | (80, 4, 162), 118 | (125, 9, 162), 119 | (82, 4, 163), 120 | (83, 4, 164), 121 | (84, 4, 165), 122 | (85, 4, 166), 123 | (86, 4, 167), 124 | (87, 4, 168), 125 | (88, 4, 169), 126 | (89, 4, 170), 127 | (90, 4, 171), 128 | (91, 4, 172), 129 | (92, 4, 173), 130 | (93, 4, 174), 131 | (94, 4, 175), 132 | (95, 4, 176), 133 | (96, 4, 177), 134 | (97, 4, 178), 135 | (98, 4, 179), 136 | (99, 4, 180), 137 | (100, 4, 181), 138 | (130, 9, 182), 139 | (118, 9, 183), 140 | (116, 7, 184), 141 | (107, 6, 185), 142 | (108, 6, 186), 143 | (103, 6, 187), 144 | (104, 6, 188), 145 | (105, 6, 189), 146 | (106, 6, 190), 147 | (109, 6, 191), 148 | (110, 6, 192), 149 | (111, 6, 193), 150 | (112, 6, 194), 151 | (115, 7, 194), 152 | (120, 9, 194), 153 | (113, 7, 195), 154 | (114, 7, 196), 155 | (124, 9, 513), 156 | (126, 9, 582), 157 | (117, 9, 951); 158 | 159 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 160 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 161 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 162 | -------------------------------------------------------------------------------- /database/complete-dump/single-tables/wm_formula_svg_missing.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.5.4 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 134.0.30.203:3306 6 | -- Erstellungszeit: 31. Jul 2014 um 17:42 7 | -- Server Version: 5.5.28a-MariaDB 8 | -- PHP-Version: 5.3.19 9 | 10 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | 19 | -- 20 | -- Datenbank: `20080912003-1` 21 | -- 22 | 23 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 24 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 25 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 26 | -------------------------------------------------------------------------------- /database/complete-dump/single-tables/wm_invalid_formula_requests.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.5.4 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 134.0.30.203:3306 6 | -- Erstellungszeit: 31. Jul 2014 um 17:42 7 | -- Server Version: 5.5.28a-MariaDB 8 | -- PHP-Version: 5.3.19 9 | 10 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | 19 | -- 20 | -- Datenbank: `20080912003-1` 21 | -- 22 | 23 | -- 24 | -- Daten für Tabelle `wm_invalid_formula_requests` 25 | -- 26 | 27 | INSERT INTO `wm_invalid_formula_requests` (`id`, `last_request_time`, `requests`, `latex`) VALUES 28 | (3, '2014-06-21 11:27:16', 4, '$\\subseteq$'), 29 | (4, '2014-07-20 11:59:31', 5, '$\\Omega$'), 30 | (5, '2014-05-29 11:44:12', 2, '\\bat'); 31 | 32 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 33 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 34 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 35 | -------------------------------------------------------------------------------- /database/complete-dump/single-tables/wm_similarity.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.5.4 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 134.0.30.203:3306 6 | -- Erstellungszeit: 31. Jul 2014 um 17:43 7 | -- Server Version: 5.5.28a-MariaDB 8 | -- PHP-Version: 5.3.19 9 | 10 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | 19 | -- 20 | -- Datenbank: `20080912003-1` 21 | -- 22 | 23 | -- 24 | -- Daten für Tabelle `wm_similarity` 25 | -- 26 | 27 | INSERT INTO `wm_similarity` (`id`, `base_symbol`, `similar_symbol`, `comment_choice`) VALUES 28 | (2, 72, 989, '2 is the standard way to write 2 - all other stuff is done with math mode'), 29 | (3, 73, 983, '3 is the standard way to write 3 - all other stuff is done with math mode'), 30 | (5, 49, 1011, 'S and \\mathcal{S}'), 31 | (6, 33, 995, 'C and \\mathcal{C}'), 32 | (7, 37, 300, ''), 33 | (8, 160, 862, 'up[greekletter] is so useless...'), 34 | (9, 42, 315, ''), 35 | (10, 537, 978, 'completely round circle - only the scaling varies'), 36 | (11, 537, 502, 'completely round circle - width differs'), 37 | (12, 537, 518, 'completely round circle - size differs'), 38 | (13, 537, 565, ''), 39 | (14, 513, 986, ''), 40 | (15, 187, 925, ''), 41 | (16, 187, 353, ''), 42 | (17, 180, 374, ''), 43 | (18, 82, 848, ''), 44 | (19, 87, 310, 'text[greekletter] is useless'), 45 | (20, 169, 872, 'up[greekletter]'), 46 | (21, 171, 876, 'up[greekletter]'), 47 | (22, 168, 869, 'up[greekletter]'), 48 | (23, 111, 533, 'Letter ''V'' and logical or'), 49 | (24, 111, 52, 'Capital and small letter v'); 50 | 51 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 52 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 53 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 54 | -------------------------------------------------------------------------------- /database/complete-dump/single-tables/wm_workers.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 3.5.4 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 134.0.30.203:3306 6 | -- Erstellungszeit: 31. Jul 2014 um 17:44 7 | -- Server Version: 5.5.28a-MariaDB 8 | -- PHP-Version: 5.3.19 9 | 10 | SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | 19 | -- 20 | -- Datenbank: `20080912003-1` 21 | -- 22 | 23 | -- 24 | -- Daten für Tabelle `wm_workers` 25 | -- 26 | 27 | INSERT INTO `wm_workers` (`id`, `user_id`, `API_key`, `worker_name`, `description`, `url`, `latest_heartbeat`, `status`) VALUES 28 | (4, 10, '5373c82a78230', 'DTW', 0x5468697320636c69656e74206170706c69657320612044545720746563686e697175652e, 'http://www.martin-thoma.de/write-math/clients/dtw-php/', '2014-06-29 15:33:50', 'active'), 29 | (6, 10, '5391dae1c8e99', 'DTW-Python', '', 'http://2e66a37b.ngrok.com/dtw-python/', '2014-06-06 17:39:17', 'deactivated'); 30 | 31 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 32 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 33 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 34 | -------------------------------------------------------------------------------- /database/import_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | import pymysql 6 | import pymysql.cursors 7 | import yaml 8 | import subprocess 9 | from contextlib import closing 10 | import gzip 11 | import os 12 | import time 13 | import natsort 14 | 15 | 16 | def main(mysql, folder): 17 | """ 18 | Creates the required MySQL tables in a given mysql database. 19 | @param mysql: A dictionary with the keys 'host', 'user', 'passwd', 'db' 20 | @param folder: 21 | 22 | This function assumes a dicrectory structure like this: 23 | [folder]/structure/write-math.sql 24 | [folder]/structure/foreign-keys.sql 25 | [folder]/complete-dump/single-tables/[files with .sql or .sql.gz ending] 26 | """ 27 | connection = pymysql.connect(host=mysql['host'], 28 | user=mysql['user'], 29 | passwd=mysql['passwd'], 30 | db=mysql['db'], 31 | cursorclass=pymysql.cursors.DictCursor) 32 | with closing(connection.cursor()) as cursor: 33 | # Import schema 34 | print("Import schema") 35 | with open(folder + 'structure/write-math.sql') as f: 36 | schema_sql_queries = f.read() 37 | cursor.execute(schema_sql_queries) 38 | print("schema import done") 39 | 40 | prefix = folder + "complete-dump/single-tables/" 41 | 42 | files = os.listdir(prefix) 43 | tables = filter(lambda n: n.endswith('.sql'), files) 44 | tables += filter(lambda n: n.endswith('.gz'), files) 45 | tables = natsort.natsorted(tables) 46 | 47 | big = filter(lambda n: n.startswith('wm_raw_draw_data'), tables) 48 | small = filter(lambda n: not n.startswith('wm_raw_draw_data'), tables) 49 | 50 | for table in small+big: 51 | start_time = time.time() 52 | print("Import Table '%s'" % table, end="") 53 | proc = subprocess.Popen(["mysql", 54 | "--user=%s" % mysql['user'], 55 | "--password=%s" % mysql['passwd'], 56 | "--host=%s" % mysql['host'], 57 | "%s" % mysql['db']], 58 | stdin=subprocess.PIPE, 59 | stdout=subprocess.PIPE) 60 | if table.endswith(".gz"): 61 | out, err = proc.communicate(gzip.open(prefix+table).read()) 62 | else: 63 | out, err = proc.communicate(open(prefix+table).read()) 64 | if out != "" and out is not None: 65 | print("out:") 66 | print(out) 67 | if err != "" and err is not None: 68 | print("err") 69 | print(err) 70 | elapsed_time = time.time() - start_time 71 | print("... done in %0.2f s" % elapsed_time) 72 | proc = subprocess.Popen(["mysql", 73 | "--user=%s" % mysql['user'], 74 | "--password=%s" % mysql['passwd'], 75 | "--host=%s" % mysql['host'], 76 | "%s" % mysql['db']], 77 | stdin=subprocess.PIPE, 78 | stdout=subprocess.PIPE) 79 | out, err = proc.communicate("SET foreign_key_checks = 0;" + 80 | open(prefix+'wm_renderings.sql').read() + 81 | open(prefix+'wm_formula.sql').read()) 82 | 83 | print("Add foreign keys:") 84 | with closing(connection.cursor()) as cursor: 85 | with open('structure/foreign-keys.sql') as f: 86 | schema_sql_queries = f.read() 87 | cursor.execute(schema_sql_queries) 88 | connection.close() 89 | 90 | if __name__ == '__main__': 91 | from argparse import ArgumentParser 92 | 93 | parser = ArgumentParser() 94 | 95 | # Add more options if you like 96 | parser.add_argument("-f", "--folder", 97 | dest="folder", 98 | default="/var/www/write-math/database/", 99 | help="folder with all mysql tables", metavar="FILE") 100 | 101 | args = parser.parse_args() 102 | yamlfile = "/var/www/write-math/website/clients/python/db.config.yml" 103 | with open(yamlfile, 'r') as ymlfile: 104 | cfg = yaml.load(ymlfile) 105 | main(cfg['mysql_local'], args.folder) 106 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | Most of the scripts that were here, were moved to the 2 | [`hwrt` project](https://github.com/MartinThoma/hwrt) 3 | 4 | * `create_testset_online_once.py` should be executed on a regular basis -------------------------------------------------------------------------------- /tools/cleanup/create_testset_online_once.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Add `is_in_testset` to raw_datasets in MySQL database, so that at least 10% 6 | of the data online has the flag `is_in_testset`. 7 | """ 8 | 9 | import pymysql 10 | import pymysql.cursors 11 | import random 12 | import math 13 | 14 | # hwrt modules 15 | from hwrt.handwritten_data import HandwrittenData 16 | import hwrt.utils as utils 17 | import hwrt.filter_dataset as filter_dataset 18 | 19 | 20 | def main(mysql, symbol_yml_file): 21 | """Add testset flag to recordings in MySQL database.""" 22 | connection = pymysql.connect(host=mysql['host'], 23 | user=mysql['user'], 24 | passwd=mysql['passwd'], 25 | db=mysql['db'], 26 | cursorclass=pymysql.cursors.DictCursor) 27 | cursor = connection.cursor() 28 | 29 | # Get IDs of symbols we want to create testset for 30 | metadata = filter_dataset.get_metadata() 31 | datasets = filter_dataset.get_symbol_ids(symbol_yml_file, metadata) 32 | 33 | for i, data in enumerate(datasets): 34 | fid, formula_in_latex = data['id'], data['formula_in_latex'] 35 | print("%i: Create testset for %s (id: %i)..." % (i, 36 | formula_in_latex, 37 | fid)) 38 | sql = ("SELECT `id`, `is_in_testset` FROM `wm_raw_draw_data` " 39 | "WHERE `accepted_formula_id` = %i" % fid) 40 | cursor.execute(sql) 41 | raw_datasets = cursor.fetchall() 42 | is_in_testset = 0 43 | raw_candidate_ids = [] 44 | for raw_data in raw_datasets: 45 | if raw_data['is_in_testset'] == 1: 46 | is_in_testset += 1 47 | else: 48 | raw_candidate_ids.append(raw_data['id']) 49 | testset_ratio = 0.1 50 | testset_total = int(math.ceil(len(raw_datasets) * testset_ratio)) 51 | remaining = testset_total - is_in_testset 52 | 53 | if remaining > 0: 54 | print(("\t%i in testset. " 55 | "Add remaining %i datasets to testset...") % 56 | (is_in_testset, remaining)) 57 | add_new = random.sample(raw_candidate_ids, remaining) 58 | if len(add_new) < 20: 59 | for el in add_new: 60 | print("\thttp://write-math.com/view/?raw_data_id=%i" % el) 61 | for rid in add_new: 62 | sql = ("UPDATE `wm_raw_draw_data` SET `is_in_testset`=1 " 63 | "WHERE `id` = %i LIMIT 1") % rid 64 | cursor.execute(sql) 65 | connection.commit() 66 | 67 | 68 | def get_parser(): 69 | """Return the parser object for this script.""" 70 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 71 | parser = ArgumentParser(description=__doc__, 72 | formatter_class=ArgumentDefaultsHelpFormatter) 73 | parser.add_argument("-s", "--symbol", 74 | dest="symbol_filename", 75 | type=lambda x: utils.is_valid_file(parser, x), 76 | required=True, 77 | help="symbol yml file", 78 | metavar="FILE") 79 | return parser 80 | 81 | 82 | if __name__ == '__main__': 83 | args = get_parser().parse_args() 84 | cfg = utils.get_database_configuration() 85 | if 'mysql_online' in cfg: 86 | main(cfg['mysql_online'], args.symbol_filename) 87 | if 'mysql_local' in cfg: 88 | main(cfg['mysql_local'], args.symbol_filename) 89 | -------------------------------------------------------------------------------- /tools/cleanup/normalize_formulas_online.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Normalize the data in `wm_formula`.`formula_in_latex` 6 | 7 | This is only useful for write-math.com 8 | """ 9 | 10 | import pymysql 11 | import pymysql.cursors 12 | 13 | # hwrt modules 14 | import hwrt.latex as latex 15 | import hwrt.utils as utils 16 | 17 | 18 | def main(mysql): 19 | connection = pymysql.connect(host=mysql['host'], 20 | user=mysql['user'], 21 | passwd=mysql['passwd'], 22 | db=mysql['db'], 23 | cursorclass=pymysql.cursors.DictCursor) 24 | cursor = connection.cursor() 25 | 26 | # Download all formula_in_latex 27 | sql = "SELECT `id`, `formula_in_latex` FROM `wm_formula` " 28 | cursor.execute(sql) 29 | datasets = cursor.fetchall() 30 | 31 | j = 1 32 | 33 | latex2id = {} 34 | for data in datasets: 35 | fid, formula_in_latex = data['id'], data['formula_in_latex'] 36 | latex2id[formula_in_latex] = fid 37 | 38 | for i, data in enumerate(datasets, start=1433): 39 | fid, formula_in_latex = data['id'], data['formula_in_latex'] 40 | latex2id[formula_in_latex] = fid 41 | if latex.normalize(formula_in_latex) in latex2id: 42 | newid = latex2id[latex.normalize(formula_in_latex)] 43 | else: 44 | newid = 0 45 | if latex.normalize(formula_in_latex) != formula_in_latex: 46 | print("## %i (%i -> %i)" % (j, fid, newid)) 47 | print("\t%s changes to" % formula_in_latex) 48 | print("\t%s" % latex.normalize(formula_in_latex)) 49 | if newid == 0: 50 | print("\tNo collision: Change automatically") 51 | sql = ("UPDATE `wm_formula` " 52 | "SET `formula_in_latex` = %s, " 53 | "`formula_name` = %s " 54 | "WHERE `wm_formula`.`id` = %s LIMIT 1;") 55 | cursor.execute(sql, (latex.normalize(formula_in_latex), 56 | latex.normalize(formula_in_latex), 57 | fid)) 58 | connection.commit() 59 | j += 1 60 | if j > 20: 61 | # Make sure not too much gets messed up if something goes wrong 62 | break 63 | 64 | 65 | def get_parser(): 66 | """Return the parser object for this script.""" 67 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 68 | parser = ArgumentParser(description=__doc__, 69 | formatter_class=ArgumentDefaultsHelpFormatter) 70 | return parser 71 | 72 | 73 | if __name__ == '__main__': 74 | args = get_parser().parse_args() 75 | cfg = utils.get_database_configuration() 76 | if 'mysql_online' in cfg: 77 | main(cfg['mysql_online']) 78 | # if 'mysql_local' in cfg: 79 | # main(cfg['mysql_local']) 80 | -------------------------------------------------------------------------------- /tools/dtw_classifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Classify data with a greedy algorithm that is simmilar to DTW (but not DTW!). 6 | """ 7 | 8 | import HandwrittenData 9 | import distance_metric 10 | from collections import defaultdict 11 | 12 | 13 | class DtwClassifier(object): 14 | """ 15 | A classifier which makes use of dynamic time warping. 16 | """ 17 | def __init__(self, threshold=1000000): 18 | self.datasets = [] 19 | # Maximum distance a symbol may have 20 | self.threshold = threshold 21 | self.datasets_counter = defaultdict(int) # count data by symbol_id 22 | 23 | def learn(self, trainingdata): 24 | """ 25 | Parameters 26 | ---------- 27 | trainingdata: dict 28 | has keys 'handwriting', 'formula_id', 'id', 'formula_in_latex' 29 | """ 30 | assert type(trainingdata) is list 31 | for data in trainingdata: 32 | assert 'formula_in_latex' in data 33 | assert 'handwriting' in data 34 | assert isinstance(data['handwriting'], 35 | HandwrittenData.HandwrittenData), \ 36 | ("handwritten data is not of type HandwrittenData, " 37 | "but of %r") % type(data['handwriting']) 38 | if self.datasets_counter[data['handwriting'].formula_id] < 50: 39 | self.datasets_counter[data['handwriting'].formula_id] += 1 40 | self.datasets.append(data) 41 | 42 | def classify(self, a): 43 | """ 44 | Classify a with data from datasets and smoothing of EPSILON. 45 | 46 | Parameters 47 | ---------- 48 | a : list 49 | List of points 50 | 51 | Returns 52 | ------- 53 | list : 54 | List of possible classifications, ordered DESC by likelines 55 | """ 56 | 57 | assert type(a) is HandwrittenData.HandwrittenData 58 | 59 | # key: formula_id, value: (dtw, Handwriting) (lowest prefered) 60 | best_by_symbol = {} 61 | 62 | for dataset in self.datasets: 63 | b = dataset['handwriting'] 64 | d = distance_metric.handwritten_data_greedy_matching_distance(a, b) 65 | if d < self.threshold: 66 | if b.formula_id in best_by_symbol: 67 | if d < best_by_symbol[b.formula_id]: 68 | best_by_symbol[b.formula_id] = (d, b) 69 | else: 70 | best_by_symbol[b.formula_id] = (d, b) 71 | 72 | results = [] 73 | for _, tmp in best_by_symbol.items(): 74 | d, b = tmp 75 | results.append({'p': -1, 76 | 'dtw': d, 77 | 'formula_id': b.formula_id, 78 | 'handwriting': b}) 79 | results = sorted(results, key=lambda k: k['dtw'])[:10] 80 | 81 | def get_probability_from_distance(results): 82 | """ Get a list of results with dtw and formula id and return a 83 | dict mapping formula-ids to probabilities. 84 | """ 85 | numeric_factor = 100 86 | distances = [-result['dtw']/numeric_factor for result in results] 87 | softmax_results = distance_metric.softmax(distances) 88 | probabilities = [] 89 | for dictionary, p in zip(results, softmax_results): 90 | probabilities.append({'formula_id': dictionary, 'p': p}) 91 | return sorted(probabilities, key=lambda k: k['p'], reverse=True) 92 | 93 | return get_probability_from_distance(results) 94 | -------------------------------------------------------------------------------- /tools/find_wrong_segmentation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Check single symbol recordings for multiple symbols. 6 | 7 | This tool tries to find recordings which have a segmentation online which 8 | says the most likely hypothesis is only one symbol, but actually - with the 9 | current model - the most likely hypothesis has multiple symbols. 10 | """ 11 | 12 | from hwrt import segmentation as se 13 | from hwrt import utils 14 | 15 | # import json 16 | import pickle 17 | import natsort 18 | 19 | import logging 20 | import sys 21 | import os 22 | 23 | logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 24 | level=logging.DEBUG, 25 | stream=sys.stdout) 26 | 27 | 28 | def main(raw_pickle): 29 | logging.info('Start loading raw datasets...') 30 | raw_datasets = load_raw(raw_pickle) 31 | logging.info('Start analyzing...') 32 | wrongs = [] 33 | for i, raw_dataset in enumerate(raw_datasets): 34 | if i % 100 == 0: 35 | print(i) 36 | ret = check_single(raw_dataset) 37 | if ret != -1: 38 | wrongs.append(ret) 39 | logging.info("Wrongs: %i", len(wrongs)) 40 | 41 | 42 | def check_single(raw_dataset): 43 | strokelist = raw_dataset['handwriting'].get_sorted_pointlist() 44 | if len(strokelist) > 8: 45 | logging.warning('%i strokes in %s', 46 | len(strokelist), 47 | raw_dataset['handwriting']) 48 | beam = se.Beam() 49 | for stroke in strokelist: 50 | beam.add_stroke({'data': [stroke], 'id': 42}) # TODO 51 | seg_predict = beam.get_results() 52 | if seg_predict[0]['symbol count'] != 1: 53 | logging.info(('http://write-math.com/view/?raw_data_id=%i : ' 54 | '%s symbols (%s)'), 55 | raw_dataset['handwriting'].raw_data_id, 56 | seg_predict[0]['symbol count'], 57 | seg_predict[0]['semantics'].split(';')[1]) 58 | return (raw_dataset, seg_predict) 59 | return -1 60 | 61 | 62 | def load_raw(path_to_data): 63 | loaded = pickle.load(open(path_to_data, "rb")) 64 | raw_datasets = loaded['handwriting_datasets'] 65 | return raw_datasets 66 | 67 | 68 | def is_valid_file(parser, arg): 69 | """Check if arg is a valid file that already exists on the file 70 | system. 71 | """ 72 | arg = os.path.abspath(arg) 73 | if not os.path.exists(arg): 74 | parser.error("The file %s does not exist!" % arg) 75 | else: 76 | return arg 77 | 78 | 79 | def _get_default_pickle(): 80 | project_root = utils.get_project_root() 81 | raw_dir = os.path.join(project_root, "raw-datasets") 82 | models = filter(lambda n: n.endswith(".pickle"), os.listdir(raw_dir)) 83 | models = natsort.natsorted(models, reverse=True) 84 | if len(models) == 0: 85 | return None 86 | else: 87 | return os.path.join(raw_dir, models[0]) 88 | 89 | 90 | def get_parser(): 91 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 92 | parser = ArgumentParser(description=__doc__, 93 | formatter_class=ArgumentDefaultsHelpFormatter) 94 | parser.add_argument("-r", "--raw", 95 | dest="raw_pickle", 96 | default=_get_default_pickle(), 97 | type=lambda x: is_valid_file(parser, x), 98 | help="load data from RAW_PICKLE_FILE", 99 | metavar="RAW_PICKLE_FILE") 100 | return parser 101 | 102 | 103 | if __name__ == '__main__': 104 | args = get_parser().parse_args() 105 | main(args.raw_pickle) 106 | -------------------------------------------------------------------------------- /tools/find_wrong_symbol_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Find recordings where the symbol count does not match the segmentation.""" 5 | 6 | import sys 7 | import logging 8 | logging.basicConfig(level=logging.INFO, 9 | stream=sys.stdout, 10 | format='%(asctime)s %(levelname)s: %(message)s') 11 | logger = logging.getLogger(__name__) 12 | 13 | import json 14 | 15 | # Database stuff 16 | import pymysql 17 | import pymysql.cursors 18 | 19 | # My modules 20 | from hwrt import utils 21 | 22 | 23 | def main(): 24 | cfg = utils.get_database_configuration() 25 | mysql = cfg['mysql_online'] 26 | find_wrong_count(mysql) 27 | 28 | 29 | def find_wrong_count(mysql): 30 | connection = pymysql.connect(host=mysql['host'], 31 | user=mysql['user'], 32 | passwd=mysql['passwd'], 33 | db=mysql['db'], 34 | cursorclass=pymysql.cursors.DictCursor) 35 | cursor = connection.cursor() 36 | offset = 0 37 | bucket_size = 100 38 | sql = ("SELECT COUNT(`id`) as `count` FROM `wm_raw_draw_data` " 39 | "WHERE `stroke_segmentable` = 1 AND `segmentation` IS NOT NULL " 40 | "AND `classifiable` = 1 AND accepted_formula_id != 1 ") 41 | cursor.execute(sql) 42 | recordings_total = int(cursor.fetchall()[0]['count']) 43 | logging.info("Recordings (total): %i", recordings_total) 44 | while offset < recordings_total: 45 | fixed = 0 46 | sql = ("SELECT `id`, `segmentation`, `nr_of_symbols`, " 47 | "`wild_point_count` " 48 | "FROM `wm_raw_draw_data` " 49 | "WHERE `stroke_segmentable` = 1 AND `segmentation` IS NOT NULL " 50 | "AND `classifiable` = 1 AND accepted_formula_id != 1 " 51 | "ORDER BY `id` LIMIT %i, %i" % (offset, bucket_size)) 52 | cursor.execute(sql) 53 | recordings = cursor.fetchall() 54 | for recording in recordings: 55 | segmentation = json.loads(recording['segmentation']) 56 | nr_of_symbols = int(recording['nr_of_symbols']) 57 | wildpoints = int(recording['wild_point_count']) 58 | if nr_of_symbols != len(segmentation): 59 | # print(('http://www.martin-thoma.de/write-math/' 60 | # 'view/?raw_data_id=%i (should be %i)') % 61 | # (recording['id'], len(segmentation))) 62 | fix_symbol_count(mysql, 63 | int(recording['id']), 64 | len(segmentation)) 65 | fixed += 1 66 | elif nr_of_symbols - wildpoints <= 0: 67 | print(('http://www.martin-thoma.de/write-math/' 68 | 'view/?raw_data_id=%i (wildpoints wrong)') % 69 | (recording['id'])) 70 | offset += bucket_size 71 | logging.info('Offset: %i (fixed %i)', offset, fixed) 72 | 73 | 74 | def fix_symbol_count(mysql, wid, correct_count): 75 | connection = pymysql.connect(host=mysql['host'], 76 | user=mysql['user'], 77 | passwd=mysql['passwd'], 78 | db=mysql['db'], 79 | cursorclass=pymysql.cursors.DictCursor) 80 | cursor = connection.cursor() 81 | sql = (("UPDATE `wm_raw_draw_data` " 82 | "SET `nr_of_symbols` = %i " 83 | "WHERE `wm_raw_draw_data`.`id` =%i " 84 | "LIMIT 1;") % (correct_count, wid)) 85 | cursor.execute(sql) 86 | connection.commit() 87 | cursor.close() 88 | connection.close() 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /tools/is_one_symbol_classifier.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/is_one_symbol_classifier.pickle -------------------------------------------------------------------------------- /tools/language_model/find_mathmode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Get math mode environments from text file.""" 5 | 6 | import os 7 | import re 8 | import codecs 9 | import logging 10 | 11 | 12 | def main(filename): 13 | math_mode = get_math_mode(filename) 14 | for i, el in enumerate(math_mode): 15 | print("%i.\t%s" % (i, el.replace('\n', '\\n'))) 16 | 17 | 18 | def get_math_mode(filename): 19 | """ 20 | Read `filename` and get all math mode contents from it. 21 | 22 | Parameters 23 | ---------- 24 | filename : str 25 | Path to a TeX file 26 | 27 | Returns 28 | ------- 29 | list of math mode contents 30 | """ 31 | with codecs.open(filename, 'r', 'utf-8') as f: 32 | lines = f.read() 33 | 34 | lines = extract_document_body(lines) 35 | if len(lines) == 0: 36 | return [] 37 | elif len(lines) > 1: 38 | logging.debug("File '%s' has %i document environments" % 39 | (filename, len(lines))) 40 | else: 41 | lines = lines[0] 42 | 43 | # strip comment lines 44 | lines = lines.split("\n") 45 | new_lines = [] 46 | for line in lines: 47 | if not line.strip().startswith("%"): 48 | new_lines.append(line) 49 | lines = "\n".join(new_lines) 50 | 51 | # match mathmode 52 | p1 = re.compile('\$(.*?)\$', re.DOTALL) 53 | p2 = re.compile('\\[(.+?)\\\]') 54 | matches = p1.findall(lines) 55 | matches += p2.findall(lines) 56 | return matches 57 | 58 | 59 | def extract_environments(env, text): 60 | """ 61 | Get the content of all environments 'environment' as a list. 62 | 63 | Parameters 64 | ---------- 65 | env : string 66 | Name of the environment 67 | text : string 68 | Text to parse for environment 69 | 70 | Returns 71 | ------- 72 | list 73 | List of matches 74 | """ 75 | document = re.compile('\\\\begin{%s}(.*?)\\\\end{%s}' % (env, env), 76 | re.MULTILINE | re.DOTALL) 77 | a = document.findall(text) 78 | return a 79 | 80 | 81 | def extract_document_body(text): 82 | return extract_environments('document', text) 83 | 84 | 85 | def unfold_math(expression): 86 | tree = {} 87 | return tree 88 | 89 | 90 | def is_valid_file(parser, arg): 91 | """Check if arg is a valid file that already exists on the file 92 | system. 93 | """ 94 | arg = os.path.abspath(arg) 95 | if not os.path.exists(arg): 96 | parser.error("The file %s does not exist!" % arg) 97 | else: 98 | return arg 99 | 100 | 101 | def get_parser(): 102 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 103 | parser = ArgumentParser(description=__doc__, 104 | formatter_class=ArgumentDefaultsHelpFormatter) 105 | parser.add_argument("-f", "--file", 106 | dest="filename", 107 | required=True, 108 | type=lambda x: is_valid_file(parser, x), 109 | help="write report to FILE", 110 | metavar="FILE") 111 | return parser 112 | 113 | 114 | if __name__ == "__main__": 115 | args = get_parser().parse_args() 116 | main(args.filename) 117 | -------------------------------------------------------------------------------- /tools/language_model/get_vocabulary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Get a list of all single symbols. 6 | """ 7 | 8 | import pymysql.cursors 9 | import codecs 10 | 11 | from hwrt import utils 12 | 13 | 14 | def main(): 15 | """ 16 | Get a list of formulas. 17 | 18 | Parameters 19 | ---------- 20 | cursor : a database cursor 21 | dataset : string 22 | Either 'all' or a path to a yaml symbol file. 23 | 24 | Returns 25 | ------- 26 | list : 27 | A list of formulas 28 | """ 29 | cfg = utils.get_database_configuration() 30 | mysql = cfg['mysql_online'] 31 | connection = pymysql.connect(host=mysql['host'], 32 | user=mysql['user'], 33 | passwd=mysql['passwd'], 34 | db=mysql['db'], 35 | cursorclass=pymysql.cursors.DictCursor, 36 | charset='utf8') 37 | cursor = connection.cursor() 38 | sql = ("SELECT `id`, `formula_in_latex` FROM `wm_formula` " 39 | # "WHERE `formula_type` = 'single symbol' " 40 | "WHERE `formula_type` = 'nesting symbol' " 41 | "ORDER BY `formula_in_latex` ASC") 42 | cursor.execute(sql) 43 | symbols = cursor.fetchall() 44 | store_symbols(symbols) 45 | 46 | 47 | def store_symbols(symbols): 48 | with codecs.open('vocabulary.txt', 'w', 'utf-8') as f: 49 | for symbol in symbols: 50 | f.write("%s\n" % symbol['formula_in_latex']) 51 | 52 | 53 | def get_parser(): 54 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 55 | parser = ArgumentParser(description=__doc__, 56 | formatter_class=ArgumentDefaultsHelpFormatter) 57 | parser.add_argument("-s", "--symbol", 58 | dest="symbol_filename", 59 | type=lambda x: utils.is_valid_file(parser, x), 60 | required=True, 61 | help="symbol yml file", 62 | metavar="FILE") 63 | return parser 64 | 65 | 66 | if __name__ == "__main__": 67 | args = get_parser().parse_args() 68 | main() 69 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LatexMathSymbolCounter", "LatexMathSymbolCounter\LatexMathSymbolCounter.csproj", "{6007BEF6-9DA7-4041-8BE7-6C98643808AB}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6007BEF6-9DA7-4041-8BE7-6C98643808AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6007BEF6-9DA7-4041-8BE7-6C98643808AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6007BEF6-9DA7-4041-8BE7-6C98643808AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6007BEF6-9DA7-4041-8BE7-6C98643808AB}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter.v12.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter.v12.suo -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/LatexMathSymbolCounter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6007BEF6-9DA7-4041-8BE7-6C98643808AB} 8 | Exe 9 | Properties 10 | LatexMathSymbolCounter 11 | lmsc 12 | v3.5 13 | 512 14 | Client 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/LatexMathSymbolCounter.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | "D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LaTeX-examples" 5 | 6 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LatexMathSymbolCounter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LatexMathSymbolCounter")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fb0915bf-c495-46c2-a2d2-3b9afde6f587")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/LatexMathSymbolCounter.csproj.FileListAbsolute.txt: -------------------------------------------------------------------------------- 1 | D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LatexMathSymbolCounter\LatexMathSymbolCounter\bin\Debug\lmsc.exe.config 2 | D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LatexMathSymbolCounter\LatexMathSymbolCounter\bin\Debug\lmsc.exe 3 | D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LatexMathSymbolCounter\LatexMathSymbolCounter\bin\Debug\lmsc.pdb 4 | D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LatexMathSymbolCounter\LatexMathSymbolCounter\obj\Debug\lmsc.exe 5 | D:\Dev - Running Programms\CSharp\Programme\LatexCounter\LatexMathSymbolCounter\LatexMathSymbolCounter\obj\Debug\lmsc.pdb 6 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/lmsc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/lmsc.exe -------------------------------------------------------------------------------- /tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/lmsc.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/language_model/language-model-building/LatexMathSymbolCounter/LatexMathSymbolCounter/obj/Debug/lmsc.pdb -------------------------------------------------------------------------------- /tools/language_model/language-model-building/README.md: -------------------------------------------------------------------------------- 1 | Try to build up a language model for mathematics. 2 | 3 | ## Simple Word Count 4 | 5 | -: 1215540 6 | \in: 314280 7 | \gamma: 286200 8 | /: 268920 9 | \mathbb{R}: 212220 10 | +: 203040 11 | \rightarrow: 127980 12 | \varphi: 115020 13 | |: 101520 14 | \circ: 96660 15 | \subseteq: 89100 16 | \angle: 70200 17 | \mathfrak{T}: 66960 18 | \kappa: 63720 19 | \cap: 63180 20 | >: 62100 21 | \partial: 59940 22 | \cdot: 57780 23 | \pi: 57240 24 | \dots: 56160 25 | \neq: 47520 26 | \times: 45900 27 | \emptyset: 45360 28 | \sigma: 43200 29 | \mapsto: 41040 30 | \Leftrightarrow: 39420 31 | \Delta: 38880 32 | \mathfrak{B}: 37800 33 | \setminus: 35100 34 | \forall: 34020 35 | <: 33480 36 | \cup: 32400 37 | \infty: 32400 38 | \bigcup: 31860 39 | \delta: 30780 40 | \lambda: 27540 41 | \sim: 27540 42 | \mathcal{P}: 25920 43 | \triangle: 25380 44 | \varepsilon: 24840 45 | \|: 24840 46 | \mathcal{S}: 23220 47 | \langle: 22140 48 | \alpha: 21600 49 | \rangle: 21600 50 | \mathbb{Z}: 20520 51 | \mathfrak{U}: 20520 52 | \Im: 20520 53 | \leq: 19980 54 | \sum: 19980 55 | \mapsfrom: 18360 56 | \dot{\cup}: 17820 57 | \blacksquare: 17820 58 | \sphericalangle: 17820 59 | \chi: 17280 60 | \cong: 15660 61 | \varrho: 15120 62 | \mathbb{H}: 15120 63 | \mathbb{N}: 14040 64 | \exists: 12960 65 | \geq: 11880 66 | \notin: 7560 67 | \measuredangle: 6480 68 | \beta: 6480 69 | \int: 6480 70 | \mid: 6480 71 | \mathbb{Q}: 5940 72 | \perp: 5400 73 | \rho: 5400 74 | \iota: 4860 75 | \psi: 4320 76 | \Gamma: 4320 77 | \}: 3780 78 | \mathcal{A}: 3780 79 | \subsetneq: 3240 80 | \parallel: 3240 81 | \prod: 2700 82 | \pm: 2700 83 | \supseteq: 2700 84 | \ni: 2700 85 | \Leftarrow: 2700 86 | \vdots: 2700 87 | \mu: 2160 88 | \hookrightarrow: 2160 89 | \{: 2160 90 | \Omega: 1620 91 | \tau: 1620 92 | \bigcap: 1620 93 | \nparallel: 1080 94 | \Phi: 1080 95 | \colon: 540 96 | \equiv: 540 97 | \ddots: 540 98 | \nexists: 540 99 | \#: 540 100 | \&: 540 101 | \%: 540 102 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/parse_folder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import fnmatch 4 | import os 5 | from argparse import ArgumentParser 6 | import json 7 | import subprocess 8 | 9 | 10 | def combine_known_symbols(kn1, kn2): 11 | for key, value in kn2.items(): 12 | if key not in kn1: 13 | kn1[key] = value 14 | else: 15 | kn1[key] += value 16 | return kn1 17 | 18 | 19 | def parse_folder(folder): 20 | matches = [] 21 | for root, dirnames, filenames in os.walk(folder): 22 | for filename in fnmatch.filter(filenames, '*.tex'): 23 | matches.append(os.path.join(root, filename)) 24 | known_symbols = {} 25 | for filename in matches: 26 | if is_latex_root(filename): 27 | print(filename) 28 | proc = subprocess.Popen(["./build-language-model.py", "-f %s" % filename, 29 | "-o out.txt"], 30 | stdout=subprocess.PIPE, shell=True) 31 | (out, err) = proc.communicate() 32 | with open("out.txt") as f: 33 | out = f.read() 34 | kn2 = json.loads(out) 35 | known_symbols = combine_known_symbols(known_symbols, kn2) 36 | return known_symbols 37 | 38 | 39 | def is_latex_root(filename): 40 | with open(filename) as f: 41 | content = f.read() 42 | return "\\documentclass" in content 43 | 44 | 45 | def print_known_symbols(known_symbols): 46 | for latex, counter in sorted(known_symbols.items(), 47 | key=lambda x: x[1], 48 | reverse=True): 49 | if counter > 0: 50 | print("%s: %i" % (latex, counter)) 51 | 52 | 53 | if __name__ == '__main__': 54 | parser = ArgumentParser() 55 | folder = '/home/moose/Downloads/LaTeX-examples/' 56 | parser.add_argument("-f", "--folder", dest="folder", 57 | default=folder, 58 | help="folder with multiple LaTeX files", 59 | metavar="FOLDER") 60 | args = parser.parse_args() 61 | known_symbols = parse_folder(args.folder) 62 | print_known_symbols(known_symbols) 63 | -------------------------------------------------------------------------------- /tools/language_model/language-model-building/parse_wikipedia.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import urllib2 4 | import parse_folder 5 | 6 | 7 | def extract_wikipedia_article(article_name): 8 | content = urllib2.urlopen('http://de.wikipedia.org/w/api.php?format=json&action=query&titles=Laplace-Operator&prop=revisions&rvprop=content').read() 9 | mathexpressions = parse_folder.extract_math_mode(content, 10 | is_wikipedia=True) 11 | for expr in mathexpressions: 12 | expr = expr.replace("\\\\", "\\") 13 | symbols = parse_folder.extract_symbols(expr) 14 | for symbol in symbols: 15 | print(symbol+" in "+expr) 16 | -------------------------------------------------------------------------------- /tools/model2json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Convert a model.json file with numpy arrays to a normal json file.""" 5 | 6 | import logging 7 | import sys 8 | import os 9 | logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 10 | level=logging.DEBUG, 11 | stream=sys.stdout) 12 | import simplejson as json 13 | from StringIO import StringIO 14 | import numpy 15 | from base64 import b64decode 16 | # mine 17 | import utils 18 | 19 | 20 | def main(model_folder, model_target="modelparams.json"): 21 | os.chdir(model_folder) 22 | 23 | # Get model as string 24 | model_file = utils.get_latest_working_model(model_folder) 25 | with open(model_file) as f: 26 | content = f.read() 27 | parsed_model = json.loads(content) 28 | 29 | # Build model representation by layers 30 | layers = [] 31 | 32 | for i, layer in enumerate(parsed_model["layers"]): 33 | logging.info("## Layer %i" % i) 34 | logging.info("Props: %s" % str(layer['_props'])) 35 | if '_mtype' in layer: 36 | logging.info("mtype: %s" % str(layer['_mtype'])) 37 | else: 38 | logging.info(layer.keys()) 39 | logging.info("param keys: %s" % str(layer['params'].keys())) 40 | key = '__numpy.cndarray__' 41 | W = numpy.load(StringIO(b64decode(layer["params"]["W"][key]))).tolist() 42 | b = numpy.load(StringIO(b64decode(layer["params"]["b"][key]))).tolist() 43 | layers.append({'W': W, 'b': b}) 44 | 45 | with open(model_target, "w") as f: 46 | f.write(json.dumps(layers)) 47 | 48 | if __name__ == "__main__": 49 | PROJECT_ROOT = utils.get_project_root() 50 | 51 | # Get latest model folder 52 | models_folder = os.path.join(PROJECT_ROOT, "models") 53 | latest_model = utils.get_latest_folder(models_folder) 54 | 55 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 56 | parser = ArgumentParser(description=__doc__, 57 | formatter_class=ArgumentDefaultsHelpFormatter) 58 | parser.add_argument("-m", "--model", 59 | dest="model", 60 | help="where is the model folder (with the info.yml)?", 61 | metavar="FILE", 62 | type=lambda x: utils.is_valid_folder(parser, x), 63 | default=latest_model) 64 | parser.add_argument("-t", "--target", 65 | dest="target", 66 | help="what name should the new parameter file have?", 67 | metavar="FILE", 68 | default="modelparams.json") 69 | args = parser.parse_args() 70 | main(args.model, args.target) 71 | -------------------------------------------------------------------------------- /tools/symbol-generator/Makefile: -------------------------------------------------------------------------------- 1 | SOURCE = symbol 2 | DELAY = 80 3 | DENSITY = 300 4 | WIDTH = 512 5 | 6 | make: 7 | pdflatex $(SOURCE).tex -output-format=pdf 8 | make clean 9 | 10 | clean: 11 | rm -rf $(TARGET) *.class *.html *.log *.aux *.data *.gnuplot *.tmp *.pdf *.svg *.pyc tmp.tex *.dvi 12 | 13 | gif: 14 | pdfcrop $(SOURCE).pdf 15 | convert -verbose -delay $(DELAY) -loop 0 -density $(DENSITY) $(SOURCE)-crop.pdf $(SOURCE).gif 16 | make clean 17 | 18 | png: 19 | make svg 20 | inkscape $(SOURCE).svg -w $(WIDTH) --export-png=$(SOURCE).png 21 | 22 | transparentGif: 23 | convert $(SOURCE).pdf -transparent white result.gif 24 | make clean 25 | 26 | svg: 27 | make 28 | #inkscape $(SOURCE).pdf --export-plain-svg=$(SOURCE).svg 29 | pdf2svg $(SOURCE).pdf $(SOURCE).svg 30 | # Necessary, as pdf2svg does not always create valid svgs: 31 | #inkscape $(SOURCE).svg --export-plain-svg=$(SOURCE).svg 32 | -------------------------------------------------------------------------------- /tools/symbol-generator/README.md: -------------------------------------------------------------------------------- 1 | This folder is used to generate SVG images of LaTeX fonts. -------------------------------------------------------------------------------- /tools/symbol-generator/manual/Makefile: -------------------------------------------------------------------------------- 1 | SOURCE = symbol 2 | DELAY = 80 3 | DENSITY = 300 4 | WIDTH = 512 5 | 6 | make: 7 | pdflatex $(SOURCE).tex -output-format=pdf 8 | make clean 9 | 10 | clean: 11 | rm -rf $(TARGET) *.class *.html *.log *.aux *.data *.gnuplot 12 | 13 | gif: 14 | pdfcrop $(SOURCE).pdf 15 | convert -verbose -delay $(DELAY) -loop 0 -density $(DENSITY) $(SOURCE)-crop.pdf $(SOURCE).gif 16 | make clean 17 | 18 | png: 19 | make svg 20 | inkscape $(SOURCE).svg -w $(WIDTH) --export-png=$(SOURCE).png 21 | 22 | transparentGif: 23 | convert $(SOURCE).pdf -transparent white result.gif 24 | make clean 25 | 26 | svg: 27 | make 28 | #inkscape $(SOURCE).pdf --export-plain-svg=$(SOURCE).svg 29 | pdf2svg $(SOURCE).pdf $(SOURCE).svg 30 | # Necessary, as pdf2svg does not always create valid svgs: 31 | #inkscape $(SOURCE).svg --export-plain-svg=$(SOURCE).svg 32 | -------------------------------------------------------------------------------- /tools/symbol-generator/manual/cavalieri-quadrature-formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/symbol-generator/manual/cavalieri-quadrature-formula.png -------------------------------------------------------------------------------- /tools/symbol-generator/manual/formula.template.tex: -------------------------------------------------------------------------------- 1 | \documentclass[varwidth=true, border=2pt]{standalone} 2 | \usepackage[utf8]{inputenc} 3 | 4 | %Math packages 5 | \usepackage{amsmath} 6 | \usepackage{amssymb} 7 | 8 | {{packages}} 9 | 10 | 11 | %document 12 | \begin{document}${{formula}}$ 13 | \end{document} -------------------------------------------------------------------------------- /tools/symbol-generator/manual/hesse-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/tools/symbol-generator/manual/hesse-matrix.png -------------------------------------------------------------------------------- /tools/symbol-generator/manual/render.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | 6 | def formula_as_file(formula, file_path, packages=""): 7 | # assert file_path.endswith(".svg") 8 | with open("formula.template.tex") as f: 9 | template = f.read() 10 | 11 | template = template.replace("{{formula}}", formula) 12 | template = template.replace("{{packages}}", packages) 13 | print(template) 14 | 15 | with open("formulatmp.tex", "w") as f: 16 | f.write(template) 17 | 18 | os.system("pdflatex formulatmp.tex -output-format=pdf") 19 | os.system("pdf2svg formulatmp.pdf formulatmp.svg") 20 | os.system("inkscape formulatmp.svg -h 40 --export-png=%s" % file_path) 21 | 22 | 23 | def main(): 24 | """Run examples.""" 25 | formulas = [] 26 | formula = (r"\bold H = \begin{bmatrix}" 27 | r"\dfrac{\partial^2 f}{\partial x_1^2} & " 28 | r"\dfrac{\partial^2 f}{\partial x_1\,\partial x_2} & " 29 | r"\cdots & " 30 | r"\dfrac{\partial^2 f}{\partial x_1\,\partial x_n} \\[2.2ex]" 31 | r"\dfrac{\partial^2 f}{\partial x_2\,\partial x_1} & " 32 | r"\dfrac{\partial^2 f}{\partial x_2^2} & " 33 | r"\cdots & " 34 | r"\dfrac{\partial^2 f}{\partial x_2\,\partial x_n} \\[2.2ex]" 35 | r"\vdots & \vdots & \ddots & \vdots \\[2.2ex]" 36 | r"\dfrac{\partial^2 f}{\partial x_n\,\partial x_1} & " 37 | r"\dfrac{\partial^2 f}{\partial x_n\,\partial x_2} & " 38 | r"\cdots & \dfrac{\partial^2 f}{\partial x_n^2}" 39 | r"\end{bmatrix}.") 40 | formulas.append(("hesse-matrix.png", formula)) 41 | formula = (r"\int x^a\,dx = \frac{x^{a+1}}{a+1} + " 42 | r"C \qquad\text{(for } a\neq -1\text{)}\,\!") 43 | formulas.append(("cavalieri-quadrature-formula.png", formula)) 44 | for file_path, formula in formulas: 45 | formula_as_file(formula, file_path) 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /tools/symbol-generator/manual/symbol.tex: -------------------------------------------------------------------------------- 1 | \documentclass[varwidth=true, border=2pt]{standalone} 2 | \usepackage[ngerman]{babel} 3 | \usepackage[utf8]{inputenc} 4 | % Color 5 | \usepackage{color} 6 | \definecolor{lightgray}{gray}{0.95} 7 | \definecolor{gray}{gray}{0.5} 8 | 9 | %Additional packages and symbol definition 10 | \usepackage{amsmath} 11 | 12 | %document 13 | \begin{document}{\textcolor{lightgray}{a}}ü{\textcolor{lightgray}{b}} 14 | \end{document} 15 | -------------------------------------------------------------------------------- /tools/symbol-generator/symbol.tex: -------------------------------------------------------------------------------- 1 | \documentclass[varwidth=true, border=2pt]{standalone} 2 | \usepackage[utf8]{inputenc} 3 | 4 | {{ packages }} 5 | 6 | \begin{document}{{ content }} 7 | \end{document} -------------------------------------------------------------------------------- /tools/symbol-lists/tiny.yml: -------------------------------------------------------------------------------- 1 | - {latex: 'A'} 2 | - {latex: 'B'} 3 | - {latex: 'C'} -------------------------------------------------------------------------------- /tools/templates/classification-error-report.html: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 |

Classification errors

5 | The system has an error rate of {{ '%0.2f' % (classification_error*100) }}%. 6 | 7 | 10 | 11 | 15 | 16 |

Errors by correct classification

17 | 18 | {% for correct, wrongs_datasets in errors_by_correct_classification.items() %} 19 |

{{ correct }}

20 | 25 | {% endfor %} 26 | 27 |

Errors by wrong classification

28 | 29 | {% for correct, wrongs_datasets in errors_by_wrong_classification.items() %} 30 |

{{ correct }}

31 | 36 | {% endfor %} 37 | {% endblock %} -------------------------------------------------------------------------------- /tools/write_math_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from hwrt import filter_dataset 5 | 6 | 7 | def get_formulas(cursor, dataset='all'): 8 | """Get a list of formulas. 9 | 10 | Parameters 11 | ---------- 12 | cursor : a database cursor 13 | dataset : string 14 | Either 'all' or a path to a yaml symbol file. 15 | 16 | Returns 17 | ------- 18 | list : 19 | A list of formulas 20 | """ 21 | if dataset == 'all': 22 | sql = ("SELECT `id`, `formula_in_latex` FROM `wm_formula` " 23 | "ORDER BY `id` ASC") 24 | cursor.execute(sql) 25 | formulas = cursor.fetchall() 26 | else: 27 | formulas = filter_dataset.get_symbol_ids(dataset, 28 | filter_dataset.get_metadata()) 29 | return formulas 30 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | Run `composer update` to update all third-party software. 2 | 3 | 4 | ## Installation 5 | 6 | ### Apache 7 | 8 | For local testing, set `sudo sublime /etc/apache2/apache2.conf`: 9 | 10 | ```text 11 | 12 | Options Indexes FollowSymLinks ExecCGI 13 | AddHandler cgi-script .py 14 | AllowOverride All 15 | Require all granted 16 | 17 | ``` 18 | 19 | (AllowOverride is important for htaccess files) 20 | 21 | Then: 22 | 23 | ```bash 24 | $ sudo service apache2 restart 25 | ``` 26 | 27 | 28 | ### Composer 29 | 30 | This website contains some third party packages. They are administrated via 31 | composer. 32 | 33 | Install composer: 34 | 35 | ```bash 36 | curl -sS https://getcomposer.org/installer | php 37 | sudo mv composer.phar /usr/local/bin/composer 38 | ``` 39 | 40 | Now run 41 | 42 | ```bash 43 | composer update 44 | ``` 45 | 46 | ### Misc 47 | 48 | * Create `cache-data` folder. 49 | 50 | ## Status 51 | 52 | The lines of code (countet with `cloc`, excluding blank lines and comments) 53 | gives an impression how big the project is: 54 | 55 | 56 | | Date | Total | PHP | HTML | JS | CSS | Python | Remarks 57 | | ---------- | ----- | ---- | ---- | --- | --- | ------ | ------- 58 | | 2015-03-04 | 6633 | 3904 | 2533 | 133 | 42 | 19 | Initial measurement 59 | | 2015-03-06 | 6419 | 3611 | 2612 | 133 | 42 | 19 | Cleanup actions -------------------------------------------------------------------------------- /website/about/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 6 | $stmt->execute(); 7 | $user_count = $stmt->fetchObject()->user_count; 8 | 9 | $sql = "SELECT COUNT(`id`) as `formula_count` FROM `wm_formula`"; 10 | $stmt = $pdo->prepare($sql); 11 | $stmt->execute(); 12 | $formula_count = $stmt->fetchObject()->formula_count; 13 | 14 | $sql = "SELECT COUNT(`id`) as `raw_data_count` FROM `wm_raw_draw_data`"; 15 | $stmt = $pdo->prepare($sql); 16 | $stmt->execute(); 17 | $raw_data_count = $stmt->fetchObject()->raw_data_count; 18 | 19 | $sql = "SELECT COUNT(`id`) as `unclassified_count` FROM `wm_raw_draw_data` WHERE `accepted_formula_id` IS NULL"; 20 | $stmt = $pdo->prepare($sql); 21 | $stmt->execute(); 22 | $unclassified_count = $stmt->fetchObject()->unclassified_count; 23 | 24 | echo $twig->render('about.twig', array('heading' => 'About', 25 | 'file'=> "about", 26 | 'logged_in' => is_logged_in(), 27 | 'display_name' => $_SESSION['display_name'], 28 | 'msg' => $msg, 29 | 'user_count' => $user_count, 30 | 'formula_count' => $formula_count, 31 | 'raw_data_count' => $raw_data_count, 32 | 'unclassified_count' => $unclassified_count 33 | ) 34 | ); -------------------------------------------------------------------------------- /website/admin-svg-problems/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 11 | $stmt->bindParam(':fid', $_GET['formula_id'], PDO::PARAM_INT); 12 | $stmt->execute(); 13 | 14 | $sql = "SELECT `formula_id` ". 15 | "FROM `wm_formula_svg_missing` ORDER BY formula_id"; 16 | $stmt = $pdo->prepare($sql); 17 | $stmt->execute(); 18 | $id = $stmt->fetchObject()->formula_id; 19 | $str = "Location: ../admin-svg-problems/?formula_id=$id"; 20 | header($str); 21 | } 22 | 23 | $sql = "SELECT `formula_id`, `problem_type` ". 24 | "FROM `wm_formula_svg_missing`"; 25 | $stmt = $pdo->prepare($sql); 26 | $stmt->execute(); 27 | $problematic_formulas = $stmt->fetchAll(); 28 | 29 | echo $twig->render('svg-problems.twig', array('heading' => 'SVG Problems', 30 | 'file' => "user", 31 | 'logged_in' => is_logged_in(), 32 | 'display_name' => $_SESSION['display_name'], 33 | 'user_id' => $user->id, 34 | 'problematic_formulas' => $problematic_formulas, 35 | 'formula' => $formula, 36 | 'is_admin' => is_admin() 37 | ) 38 | ); 39 | 40 | ?> -------------------------------------------------------------------------------- /website/admin/phraselist.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 7 | $stmt->bindParam(':uid', $uid, PDO::PARAM_INT); 8 | $stmt->execute(); 9 | $formulas = $stmt->fetchAll(); 10 | $cachedir = '../cache-data/'; 11 | $datafile = 'phraselist.js'; 12 | $formula_list = array(); 13 | 14 | 15 | function endsWith($haystack, $needle) { 16 | // search forward starting from end minus needle length characters 17 | return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== FALSE); 18 | } 19 | 20 | 21 | foreach ($formulas as $key => $formula) { 22 | $formula = $formula['formula_in_latex']; 23 | if (strlen($formula) <= 2 || endsWith($formula, "\\")) { 24 | continue; 25 | } 26 | $formula_list[] = $formula; 27 | } 28 | $fp = fopen($cachedir.$datafile, 'w'); 29 | fwrite($fp, json_encode($formula_list)); 30 | fclose($fp); 31 | 32 | header('Location: ../admin'); 33 | ?> -------------------------------------------------------------------------------- /website/api/api.functions.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 15 | $stmt->bindParam(':recording_id', $raw_data_id, PDO::PARAM_INT); 16 | $stmt->execute(); 17 | $image_data = $stmt->fetchObject(); 18 | $total_strokes = count(json_decode($image_data->data)); 19 | return $total_strokes; 20 | } 21 | 22 | 23 | function get_answer_id($raw_data_id, $symbol_id, $strokes) { 24 | global $pdo; 25 | global $msg; 26 | 27 | // get strokes 28 | if ($strokes == 'ALL') { 29 | $total_strokes = get_stroke_count($raw_data_id); 30 | $strokes = implode(',', range(0, $total_strokes-1)); 31 | } 32 | $total_strokes = get_stroke_count($raw_data_id); 33 | $strokes = implode(',', range(0, $total_strokes-1)); 34 | $user_id = get_uid(); 35 | add_partial_classification_pure($user_id, $raw_data_id, $symbol_id, $strokes); 36 | 37 | $sql = "SELECT `id` ". 38 | "FROM `wm_partial_answer` ". 39 | "WHERE `recording_id` = :recording_id AND ". 40 | "`symbol_id` = :symbol_id LIMIT 1"; 41 | $stmt = $pdo->prepare($sql); 42 | $stmt->bindParam(':recording_id', $raw_data_id, PDO::PARAM_INT); 43 | $stmt->bindParam(':symbol_id', $symbol_id, PDO::PARAM_INT); 44 | $stmt->execute(); 45 | $answer = $stmt->fetchObject(); 46 | return $answer->id; 47 | } 48 | 49 | 50 | function adjust_user_answer_count($raw_data_id, $delta) { 51 | global $pdo; 52 | global $msg; 53 | if (!is_int($delta)) { 54 | return false; 55 | } 56 | if ($delta >= 0) { 57 | $sql = "UPDATE `wm_raw_draw_data` SET ". 58 | "`user_answers_count` = `user_answers_count` + $delta ". 59 | "WHERE `id` = :rid LIMIT 1;"; 60 | } else { 61 | $sql = "UPDATE `wm_raw_draw_data` SET ". 62 | "`user_answers_count` = `user_answers_count` $delta ". 63 | "WHERE `id` = :rid LIMIT 1;"; 64 | } 65 | 66 | $stmt = $pdo->prepare($sql); 67 | $stmt->bindParam(':rid', $raw_data_id, PDO::PARAM_INT); 68 | $result = $stmt->execute(); 69 | return $result; 70 | } 71 | 72 | 73 | function adjust_automatic_answer_count($raw_data_id, $delta) { 74 | global $pdo; 75 | global $msg; 76 | if (!is_int($delta)) { 77 | return false; 78 | } 79 | $sql = "UPDATE `wm_raw_draw_data` SET ". 80 | "`automated_answers_count` = `automated_answers_count` + ($delta) ". 81 | "WHERE `id` = :rid LIMIT 1;"; 82 | $stmt = $pdo->prepare($sql); 83 | $stmt->bindParam(':rid', $raw_data_id, PDO::PARAM_INT); 84 | $result = $stmt->execute(); 85 | return $result; 86 | } 87 | 88 | 89 | function set_zero_worker_answers($raw_data_id) { 90 | global $pdo; 91 | global $msg; 92 | 93 | $sql = "UPDATE `wm_raw_draw_data` SET ". 94 | "`automated_answers_count` = 0 ". 95 | "WHERE `id` = :rid LIMIT 1;"; 96 | $stmt = $pdo->prepare($sql); 97 | $stmt->bindParam(':rid', $raw_data_id, PDO::PARAM_INT); 98 | $result = $stmt->execute(); 99 | return $result; 100 | } 101 | 102 | ?> -------------------------------------------------------------------------------- /website/api/save-data.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 31 | $uid = get_uid(); 32 | $stmt->bindParam(':user_id', $uid, PDO::PARAM_INT); 33 | $stmt->bindParam(':data', $data, PDO::PARAM_STR); 34 | $stmt->bindParam(':user_agent', 35 | $_SERVER['HTTP_USER_AGENT'], 36 | PDO::PARAM_STR); 37 | $stmt->bindParam(':secret', $secret, PDO::PARAM_STR); 38 | $stmt->execute(); 39 | $record_id = $pdo->lastInsertId(); 40 | echo json_encode($record_id); 41 | } else { 42 | # Insert dataset 43 | $sql = "UPDATE `wm_raw_draw_data` ". 44 | "SET `data` = :data ". 45 | "WHERE `id` = :rid AND `secret` = :secret AND ". 46 | "UNIX_TIMESTAMP(NOW())-". 47 | "UNIX_TIMESTAMP(`creation_date`) < 60*5;"; 48 | $stmt = $pdo->prepare($sql); 49 | $uid = get_uid(); 50 | $stmt->bindParam(':rid', $record_id, PDO::PARAM_INT); 51 | $stmt->bindParam(':data', $data, PDO::PARAM_STR); 52 | $stmt->bindParam(':secret', $secret, PDO::PARAM_STR); 53 | $stmt->execute(); 54 | echo json_encode($record_id); 55 | } 56 | } 57 | } else { 58 | echo json_encode("No data received via POST."); 59 | } 60 | 61 | ?> -------------------------------------------------------------------------------- /website/api/set-segmentation.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 21 | $stmt->bindParam(':id', $raw_data_id, PDO::PARAM_INT); 22 | $stmt->execute(); 23 | $obj = $stmt->fetchObject(); 24 | $recording_point_list = pointLineList($obj->data); 25 | $segmentation = make_valid_segmentation($recording_point_list, json_decode($segmentation)); 26 | $segmentation = json_encode($segmentation); 27 | $response['segmentation'] = $segmentation; 28 | $response['segmentation_type'] = gettype($segmentation); 29 | 30 | # Update segmentation of recording 31 | $sql = "UPDATE `wm_raw_draw_data` ". 32 | "SET `segmentation` = :segmentation ". 33 | "WHERE `id` = :raw_data_id AND (`user_id` = :uid OR :uid=10);"; // TODO: Set to admin group 34 | $stmt = $pdo->prepare($sql); 35 | $uid = get_uid(); 36 | $response['uid'] = $uid; 37 | $stmt->bindParam(':raw_data_id', $raw_data_id, PDO::PARAM_INT); 38 | $stmt->bindParam(':segmentation', $segmentation); 39 | $stmt->bindParam(':uid', $uid, PDO::PARAM_INT); 40 | $result = $stmt->execute(); 41 | $response['result'] = $result; 42 | 43 | if (get_uid() == 10) { 44 | $filename = dirname(dirname(__FILE__))."/raw-data/$raw_data_id.svg"; 45 | if (file_exists($filename)) { 46 | unlink($filename); 47 | } 48 | } 49 | 50 | 51 | echo json_encode($response); 52 | } else { 53 | echo "{'error': 'No recording_id'}"; 54 | } 55 | 56 | ?> -------------------------------------------------------------------------------- /website/api/set-unaccept-partial.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 23 | $uid = get_uid(); 24 | $stmt->bindParam(':user_id', $uid, PDO::PARAM_INT); 25 | $stmt->bindParam(':answer_id', $answer_id, PDO::PARAM_INT); 26 | $stmt->execute(); 27 | 28 | if ($stmt->rowCount() == 1) { 29 | $msg[] = array("class" => "alert-success", 30 | "text" => "The answer was unaccepted."); 31 | return true; 32 | } else { 33 | $msg[] = array("class" => "alert-warning", 34 | "text" => "You could not accept that answer. ". 35 | "This happens when you try to accept ". 36 | "a classification of a formula you ". 37 | "did not write. ". 38 | "Or multiple form submission."); 39 | return false; 40 | } 41 | } 42 | 43 | if (isset($_POST['partial_answer_id'])) { 44 | $answer_id = intval($_POST['partial_answer_id']); 45 | 46 | $success = unaccept_partial_answer($answer_id); 47 | if ($success) { 48 | echo json_encode(1); 49 | } else { 50 | echo json_encode(0); 51 | } 52 | } elseif (isset($_POST['symbol_id']) && isset($_POST['raw_data_id']) && isset($_POST['strokes'])) { 53 | $answer_id = get_answer_id($_POST['raw_data_id'], $_POST['symbol_id'], $_POST['strokes']); 54 | $success = unaccept_partial_answer($answer_id); 55 | if ($success) { 56 | echo json_encode(1); 57 | } else { 58 | echo json_encode(0); 59 | } 60 | } else { 61 | echo "{'error': 'Not POSTed partial_answer_id'}"; 62 | } 63 | 64 | ?> -------------------------------------------------------------------------------- /website/api/submit-partial-answer.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 18 | $stmt->bindParam(':id', $raw_data_id, PDO::PARAM_INT); 19 | $stmt->execute(); 20 | $image_data = $stmt->fetchObject(); 21 | $total_strokes = count(json_decode($image_data->data)); 22 | $filtered_strokes = filter_strokes($_POST['strokes'], $total_strokes); 23 | if (count($filtered_strokes) > 0) { 24 | $strokes = implode(",", $filtered_strokes); 25 | echo add_partial_classification($user_id, $raw_data_id, $latex, $strokes); 26 | } else { 27 | echo '{"error": "Filtered strokes: '.count($filtered_strokes).' (total: '.$total_strokes.')"}'; 28 | } 29 | } else { 30 | echo json_encode('{"error": "Not POSTed latex_partial"}'); 31 | } 32 | 33 | ?> -------------------------------------------------------------------------------- /website/classify/index.php: -------------------------------------------------------------------------------- 1 | render('classify.twig', array('heading' => 'Classify', 13 | 'file'=> "classify", 14 | 'logged_in' => is_logged_in(), 15 | 'display_name' => $_SESSION['display_name'], 16 | 'msg' => $msg 17 | ) 18 | ); 19 | 20 | ?> -------------------------------------------------------------------------------- /website/classify/svg-template.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | {{ path }} 17 | {{ dots }} 18 | -------------------------------------------------------------------------------- /website/compare/index.php: -------------------------------------------------------------------------------- 1 | "alert-warning", 18 | "text" => "Please provide 'A' and 'B' with a raw_data_id. ". 19 | 'e.g. like this or '. 20 | 'like that.'); 21 | } else { 22 | $sql = "SELECT `id`, `data` FROM `wm_raw_draw_data` ". 23 | "WHERE `id` = :ida OR `id` = :idb"; 24 | $stmt = $pdo->prepare($sql); 25 | $stmt->bindParam(':ida', $_GET['A'], PDO::PARAM_INT); 26 | $stmt->bindParam(':idb', $_GET['B'], PDO::PARAM_INT); 27 | $stmt->execute(); 28 | $data = $stmt->fetchAll(PDO::FETCH_ASSOC); 29 | 30 | $epsilon = isset($_POST['epsilon']) ? $_POST['epsilon'] : 0; 31 | $A = $data[0]['data']; 32 | $B = $data[1]['data']; 33 | $As = scale_and_shift(pointLineList($A)); 34 | $Bs = scale_and_shift(pointLineList($B)); 35 | $dtw_distance = apply_greedy_matching_dtw_linewise($As, $Bs); 36 | 37 | $pathA = get_path($A, $epsilon); 38 | $pathB = get_path($B, $epsilon); 39 | } 40 | 41 | 42 | 43 | echo $twig->render('compare.twig', array('heading' => 'Compare', 44 | 'file'=> 'compare', 45 | 'logged_in' => is_logged_in(), 46 | 'display_name' => $_SESSION['display_name'], 47 | 'msg' => $msg, 48 | 'pathA' => $pathA, 49 | 'pathB' => $pathB, 50 | 'epsilon' => $epsilon, 51 | 'dtw_distance' => $dtw_distance 52 | ) 53 | ); 54 | 55 | ?> -------------------------------------------------------------------------------- /website/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "monolog/monolog": ">=1.0.0", 4 | "twig/twig": "1.*", 5 | "ircmaxell/password-compat": ">=1.0.3", 6 | "erusev/parsedown": "dev-master", 7 | "zeroclipboard/zeroclipboard": "~2.2" 8 | } 9 | } -------------------------------------------------------------------------------- /website/config.template.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/css/style.css: -------------------------------------------------------------------------------- 1 | /* Move down content because we have a fixed navbar that is 50px tall */ 2 | body { 3 | padding-top: 50px; 4 | padding-bottom: 20px; 5 | } 6 | 7 | /* Wikipedia image gallery */ 8 | ul.gallery { 9 | margin: 2px; 10 | padding: 2px; 11 | display: block; 12 | } 13 | 14 | li.gallerybox { 15 | vertical-align: top; 16 | display: inline-block; 17 | } 18 | 19 | li.gallerybox div.thumb { 20 | text-align: center; 21 | border: 1px solid #ccc; 22 | background-color: #f9f9f9; 23 | margin: 2px; 24 | } 25 | 26 | div.gallerytext { 27 | overflow: hidden; 28 | font-size: 94%; 29 | padding: 2px 4px; 30 | word-wrap:break-word; 31 | } -------------------------------------------------------------------------------- /website/data/2015-01-28-data/hist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | # If it is Python 2, then I want the Python 3 open function 5 | from future.builtins import open 6 | except: 7 | # If it is Python 3, then everything is fine 8 | pass 9 | 10 | import csv 11 | id_counter = {} 12 | 13 | with open('complete.csv', 'rt', newline='') as csvfile: 14 | csvreader = csv.reader(csvfile, delimiter=';', quotechar="'") 15 | next(csvreader, None) # skip the headers 16 | for row in csvreader: 17 | # print(row) 18 | # print(len(row)) 19 | symbol_id, user_id, data, user_agent = row 20 | if symbol_id in id_counter: 21 | id_counter[symbol_id] += 1 22 | else: 23 | id_counter[symbol_id] = 1 24 | 25 | for symbol_id, counter in sorted(id_counter.items(), 26 | key=lambda n: n[1], 27 | reverse=True): 28 | # print("%s: %i" % (symbol_id, counter)) 29 | print(counter) 30 | -------------------------------------------------------------------------------- /website/data/style.css: -------------------------------------------------------------------------------- 1 | abbr { 2 | border-bottom: dotted 1px black; 3 | } 4 | 5 | .testerror { 6 | font-size: 11pt; 7 | text-align: right; 8 | font-family: monospace; 9 | } 10 | 11 | #downloadtable { 12 | width: 50%; 13 | } 14 | 15 | td.centered, th.centered { 16 | text-align: center; 17 | } 18 | 19 | td.right, th.right { 20 | text-align: right; 21 | } -------------------------------------------------------------------------------- /website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/favicon.ico -------------------------------------------------------------------------------- /website/feature_extraction.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/fonts/STIXGeneral.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/fonts/STIXGeneral.woff -------------------------------------------------------------------------------- /website/fonts/STIXNonUni.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/fonts/STIXNonUni.woff -------------------------------------------------------------------------------- /website/fonts/STIXNonUniIta.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/fonts/STIXNonUniIta.woff -------------------------------------------------------------------------------- /website/forgot/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 22 | $stmt->bindParam(':email', $_GET['email'], PDO::PARAM_STR); 23 | $code = md5(rand()); 24 | $stmt->bindParam(':code', $code, PDO::PARAM_STR); 25 | $stmt->execute(); 26 | if ($stmt->rowCount() == 1) { 27 | $message = "Hello ".$display_name."\n\n". 28 | "A password reset has been requested for http://write-math.com. ". 29 | "Please click on the following link if you want to reset ". 30 | "your password:\n". 31 | "http://write-math.com/forgot/?email=".$_GET['email']."&code=$code\n\n". 32 | "If you did not request a new password you should ignore". 33 | "this email.\n\n". 34 | "Best regards,\n". 35 | "Martin Thoma"; 36 | $headers = 'From: Martin Thoma ' . PHP_EOL . 37 | 'Reply-To: Martin Thoma ' . PHP_EOL . 38 | 'X-Mailer: PHP/' . phpversion(); 39 | mail($_GET['email'], 40 | "[Write-Math] Password reset", 41 | $message, 42 | $headers 43 | ); 44 | $msg[] = array("class" => "alert-success", 45 | "text" => "An email has been sent to '".$_GET['email']. 46 | "' with a confirmation ". 47 | "code to reset your password."); 48 | } else { 49 | $msg[] = array("class" => "alert-warning", 50 | "text" => "The user '".$_GET['email']. 51 | "' is not in the database."); 52 | } 53 | } elseif (isset($_GET['email']) && isset($_GET['code'])) { 54 | $new_password = randomPassword(); 55 | $hash = password_hash($new_password, PASSWORD_BCRYPT, array("cost" => 10)); 56 | $sql = "UPDATE `wm_users` ". 57 | "SET `password` = :password, confirmation_code = '' ". 58 | "WHERE `email` = :email AND `confirmation_code` = :code;"; 59 | $stmt = $pdo->prepare($sql); 60 | $stmt->bindParam(':email', $_GET['email'], PDO::PARAM_STR); 61 | $stmt->bindParam(':code', $_GET['code'], PDO::PARAM_STR); 62 | $stmt->bindParam(':password', $hash, PDO::PARAM_STR); 63 | $stmt->execute(); 64 | if ($stmt->rowCount() == 1) { 65 | $message = "Hello\n\n". 66 | "Your new password for http://write-math.com ". 67 | "is: '".$new_password."' (without the apostrophes)\n\n". 68 | "Best regards,\n". 69 | "Martin Thoma"; 70 | $headers = 'From: Martin Thoma ' . PHP_EOL . 71 | 'Reply-To: Martin Thoma ' . PHP_EOL . 72 | 'X-Mailer: PHP/' . phpversion(); 73 | mail($_GET['email'], "[Write-Math] New password", $message, 74 | $headers); 75 | $msg[] = array("class" => "alert-success", 76 | "text" => "Congratulations. Your password was reseted. ". 77 | "An Email with your new password was sent ". 78 | "to you. You can change that in your profile."); 79 | $password_changed = true; 80 | print_r($message); 81 | } else { 82 | $msg[] = array("class" => "alert-danger", 83 | "text" => "Your password could not be resetted. ". 84 | "Please contact info@martin-thoma.de."); 85 | } 86 | } 87 | 88 | 89 | echo $twig->render('forgot.twig', array('heading' => 'Reset password', 90 | 'logged_in' => is_logged_in(), 91 | 'file'=> "forgot", 92 | 'msg' => $msg, 93 | 'password_changed' => $password_changed 94 | ) 95 | ); 96 | ?> -------------------------------------------------------------------------------- /website/formulas/.htaccess: -------------------------------------------------------------------------------- 1 | Options +Indexes 2 | IndexOptions +FancyIndexing 3 | # The following path has to be adjusted. It has to be an absolute path. 4 | # Testing: 5 | ErrorDocument 404 /write-math/website/formulas/cache-404.php 6 | # Online: 7 | # ErrorDocument 404 /write-math/formulas/cache-404.php -------------------------------------------------------------------------------- /website/formulas/cache-404.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 10 | $stmt->bindParam(':rid', $rendering_id, PDO::PARAM_INT); 11 | $stmt->execute(); 12 | $svg_obj = $stmt->fetchObject(); 13 | if ($svg_obj) { 14 | $svg = $svg_obj->svg; 15 | } else { 16 | $svg = ""; 17 | } 18 | 19 | 20 | if (strlen($svg) > 50) { 21 | file_put_contents ("../formulas/$formula_id-$rendering_id.svg", $svg); 22 | header("Location: ../formulas/$formula_id-$rendering_id.svg"); 23 | } 24 | ?> -------------------------------------------------------------------------------- /website/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icon-256.png -------------------------------------------------------------------------------- /website/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icon-64.png -------------------------------------------------------------------------------- /website/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /website/icons/Crystal_Clear_action_apply-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icons/Crystal_Clear_action_apply-gray.png -------------------------------------------------------------------------------- /website/icons/Crystal_Clear_action_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icons/Crystal_Clear_action_apply.png -------------------------------------------------------------------------------- /website/icons/accept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icons/accept.png -------------------------------------------------------------------------------- /website/icons/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icons/cross.png -------------------------------------------------------------------------------- /website/icons/offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 31 | You areoffline. 43 | 44 | 45 | -------------------------------------------------------------------------------- /website/icons/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/icons/pencil.png -------------------------------------------------------------------------------- /website/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/init.php: -------------------------------------------------------------------------------- 1 | "SET NAMES 'utf8'")); 11 | $pdo ->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 12 | } catch (PDOException $e) { 13 | echo 'Connection failed: ' . $e->getMessage(); 14 | } 15 | 16 | include_once 'session.php'; 17 | 18 | $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/templates'); 19 | $twig = new Twig_Environment($loader, array( 20 | 'cache' => dirname(__FILE__).'/cache', 21 | )); 22 | $twig->addGlobal('logged_in', is_logged_in()); 23 | $twig->addGlobal('display_name', $_SESSION['display_name']); 24 | $twig->addGlobal('account_type', $_SESSION['account_type']); 25 | $msg = array(); -------------------------------------------------------------------------------- /website/js/accept_partial.js: -------------------------------------------------------------------------------- 1 | function acceptPartialAnswer(raw_data_id, answer_id, successHandler) { 2 | $.ajax({ 3 | type: "POST", 4 | url: "../api/set-accept-partial.php", 5 | data: {'raw_data_id': raw_data_id, 'answer_id': answer_id}, 6 | success: function(data) 7 | { 8 | console.log(data); 9 | if (typeof(data.error) !== "undefined") { 10 | console.log(data.error); 11 | } else { 12 | successHandler(); 13 | } 14 | }, 15 | dataType: "json", 16 | error: function(xhr, status, error) { 17 | console.log("Error while sending the accept:"); 18 | console.log(status); 19 | console.log(error); 20 | } 21 | }); 22 | } -------------------------------------------------------------------------------- /website/js/submit_partial.js: -------------------------------------------------------------------------------- 1 | function submitPartial(raw_data_id, latex, strokes, successHandler) { 2 | $.ajax({ 3 | type: "POST", 4 | url: "../api/submit-partial-answer.php", 5 | data: {'latex_partial': latex, 'strokes': strokes, 'raw_data_id': raw_data_id}, 6 | success: function(data) 7 | { 8 | console.log(data); 9 | successHandler(); 10 | }, 11 | dataType: "json", 12 | error: function(xhr, status, error) { 13 | console.log("Error while sending the unaccept:"); 14 | console.log(status); 15 | console.log(error); 16 | } 17 | }); 18 | } -------------------------------------------------------------------------------- /website/js/symbolstrokes.js: -------------------------------------------------------------------------------- 1 | // Visualize which strokes were classified 2 | 3 | function visualizeSymbolStrokes(tag) { 4 | var strokes = tag.getAttribute('data-strokes').split(',').map(function(item) { 5 | return parseInt(item, 10); 6 | });; 7 | var svgDoc = tag.contentDocument; 8 | console.log(strokes); 9 | for (var i = 100 - 1; i >= 0; i--) { 10 | var stroke = svgDoc.getElementById('stroke'+i); 11 | //console.log(stroke); 12 | if (stroke) { 13 | if (strokes.indexOf(i) > -1) { 14 | stroke.style.stroke = '#ff0000'; 15 | } else { 16 | stroke.style.stroke = '#000000'; 17 | } 18 | //stroke.style.fill = '#000000'; 19 | //console.log("done"); 20 | }; 21 | }; 22 | //svgDoc.getElementById('stroke'+stroke_id).style.stroke 23 | } -------------------------------------------------------------------------------- /website/js/unaccept_partial.js: -------------------------------------------------------------------------------- 1 | function unacceptPartialAnswer(raw_data_id, formula_id, successHandler) { 2 | $.ajax({ 3 | type: "POST", 4 | url: "../api/set-unaccept-partial.php", 5 | data: {'partial_answer_id': formula_id}, 6 | success: function(data) 7 | { 8 | console.log(data); 9 | successHandler(); 10 | }, 11 | dataType: "json", 12 | error: function(xhr, status, error) { 13 | console.log("Error while sending the unaccept:"); 14 | console.log(status); 15 | console.log(error); 16 | } 17 | }); 18 | } -------------------------------------------------------------------------------- /website/latex.php: -------------------------------------------------------------------------------- 1 | 0 && end($arr_values) == ' ' && $chunk == ' '){ 69 | continue; 70 | } 71 | $filtered[] = $chunk; 72 | } 73 | 74 | return $filtered; 75 | } 76 | 77 | function chunks_to_string($chunks) { 78 | $string = ''; 79 | $began_context = False; 80 | $context_depth = 0; 81 | $context_triggers = ['_', '^']; 82 | foreach ($chunks as $chunk) { 83 | if ($began_context && $chunk != '{') { 84 | $string .= '{'.$chunk.'}'; 85 | $began_context = False; 86 | } else if ($began_context && $chunk == '{') { 87 | $began_context = False; 88 | $string .= $chunk; 89 | } else { 90 | if (in_array($chunk, $context_triggers)) { 91 | $began_context = True; 92 | $context_depth += 1; 93 | } 94 | $string .= $chunk; 95 | } 96 | } 97 | return $string; 98 | } 99 | 100 | function normalize($latex){ 101 | return chunks_to_string(chunk_math($latex)); 102 | } 103 | 104 | ?> -------------------------------------------------------------------------------- /website/login/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 17 | $stmt->bindParam(':email', $email, PDO::PARAM_STR); 18 | $stmt->execute(); 19 | 20 | $user = $stmt->fetchObject(); 21 | $uid = $user->id; 22 | $status = $user->status; 23 | 24 | if (!((int)$uid == $uid && (int)$uid > 0)) { 25 | $email = strtolower($email); 26 | // try again with lower case 27 | $sql = "SELECT `id`, `status`, `password` ". 28 | "FROM `wm_users` WHERE `email` = :email"; 29 | $stmt = $pdo->prepare($sql); 30 | $stmt->bindParam(':email', $email, PDO::PARAM_STR); 31 | $stmt->execute(); 32 | 33 | $user = $stmt->fetchObject(); 34 | $uid = $user->id; 35 | $status = $user->status; 36 | 37 | if (!((int)$uid == $uid && (int)$uid > 0)) { 38 | $msg[] = array("class" => "alert-warning", 39 | "text" => "Email '$email' not known."); 40 | return false; 41 | } 42 | } 43 | 44 | if ($user->id > 0 && password_verify($_POST['password'], $user->password)) { 45 | $_SESSION['email'] = $email; 46 | $_SESSION['password'] = $user->password; 47 | $_SESSION['is_logged_in'] = true; 48 | 49 | if ($ip_user) { 50 | // if an ip user logs in, his ip user account should 51 | // get merged with his new user account 52 | merge_accounts($ip_id, $user->id); 53 | } 54 | 55 | if (isset($_GET['redirect'])) { 56 | $redirect = $_GET['redirect']; 57 | } else { 58 | $redirect = '../classify'; 59 | } 60 | 61 | header("Location: $redirect"); 62 | } else { 63 | $_SESSION['is_logged_in'] = false; 64 | $msg[] = array("class" => "alert-warning", 65 | "text" => "Logging in failed. The email ". 66 | "did not match the password. Did you ". 67 | "forget your password?"); 68 | } 69 | } 70 | 71 | if (isset($_POST['email']) && $_POST['email'] != "" && isset($_POST['password'])) { 72 | login($_POST['email'], $_POST['password']); 73 | } 74 | 75 | echo $twig->render('login.twig', array('heading' => 'Login', 76 | 'logged_in' => is_logged_in(), 77 | 'display_name' => $_SESSION['display_name'], 78 | 'file'=> "login", 79 | 'msg' => $msg, 80 | 'email' => $_SESSION['email'] 81 | ) 82 | ); 83 | ?> -------------------------------------------------------------------------------- /website/maintenance.html: -------------------------------------------------------------------------------- 1 | Thank you very much for contributing to this service. 2 | 3 | I've collected 160,901 training instances the last view days. This enourmous 4 | amount of training data (almost everything from detexify) makes a server change 5 | necessary. 6 | 7 | I guess the service will be back in two days (Saturday, 19.07.2014). 8 | 9 | Thanks for contributing! -------------------------------------------------------------------------------- /website/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en", 3 | "name": "Write Math", 4 | "start_url": "/write-math/classify/", 5 | "display": "fullscreen", 6 | "orientation": "portrait" 7 | } -------------------------------------------------------------------------------- /website/nice-highres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/nice-highres.png -------------------------------------------------------------------------------- /website/niceicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MartinThoma/write-math/ece645f70341431ac7ca14740ce26ad8153a3900/website/niceicon.png -------------------------------------------------------------------------------- /website/problems/index.php: -------------------------------------------------------------------------------- 1 | = ".$r['min_wild_points']." ". 25 | "AND missing_line >= ".$r['missing_line']." ". 26 | "AND has_hook >= ".$r['has_hook']." ". 27 | "AND has_too_long_line >= ".$r['has_too_long_line']." ". 28 | "AND has_interrupted_line >= ".$r['has_interrupted_line']." ". 29 | "AND other_problem >= ".$r['other_problem']." ". 30 | "AND is_image >= ".$r['is_image']." "; 31 | } else { 32 | $r = array(); 33 | $r['min_wild_points'] = 0; 34 | $r['missing_line'] = 0; 35 | $r['has_hook'] = 0; 36 | $r['has_too_long_line'] = 0; 37 | $r['has_interrupted_line'] = 0; 38 | $r['other_problem'] = 0; 39 | $r['is_image'] = 0; 40 | } 41 | 42 | $pagination_url = "&wild_point_count=".$r['min_wild_points']. 43 | "&missing_line=".$r['missing_line']. 44 | "&has_hook=".$r['has_hook']. 45 | "&has_too_long_line=".$r['has_too_long_line']. 46 | "&has_interrupted_line=".$r['has_interrupted_line']. 47 | "&other_problem=".$r['other_problem']. 48 | "&is_image=".$r['is_image']; 49 | 50 | // Get total number of elements for pagination 51 | $sql = "SELECT COUNT(`id`) as counter FROM `wm_raw_draw_data` $where"; 52 | $stmt = $pdo->prepare($sql); 53 | $stmt->execute(); 54 | $row = $stmt->fetchObject(); 55 | $total = $row->counter; 56 | 57 | // Get all raw data of this user 58 | $currentPage = isset($_GET['page']) ? intval($_GET['page']) : 1; 59 | $sql = "SELECT `id`, `data` as `image`, `creation_date` ". 60 | "FROM `wm_raw_draw_data` ". 61 | $where. 62 | "ORDER BY `creation_date` DESC ". 63 | "LIMIT ".(($currentPage-1)*14).", 14"; 64 | $stmt = $pdo->prepare($sql); 65 | $stmt->execute(); 66 | $userimages = $stmt->fetchAll(); 67 | 68 | $tab = "all"; 69 | 70 | 71 | echo $twig->render('problems.twig', array('heading' => "Problematic raw data ($total results)", 72 | 'logged_in' => is_logged_in(), 73 | 'display_name' => $_SESSION['display_name'], 74 | 'file'=> "problems", 75 | 'userimages' => $userimages, 76 | 'total' => $total, 77 | 'pages' => ceil(($total)/14), 78 | 'currentPage' => $currentPage, 79 | 'restrictions' => $r, 80 | 'pagination_url' => $pagination_url 81 | ) 82 | ); 83 | 84 | ?> -------------------------------------------------------------------------------- /website/ranking/index.php: -------------------------------------------------------------------------------- 1 | 24*60*60) { 7 | // calculate data 8 | $sql = "SELECT `wm_users`.`id`, `display_name`, ". 9 | "COUNT(`wm_raw_draw_data`.`id`) as `written_formulas`, ". 10 | "COUNT(DISTINCT `accepted_formula_id`) as `distinct_symbols` ". 11 | "FROM `wm_raw_draw_data` ". 12 | "RIGHT JOIN `wm_users` ON `wm_users`.`id` = `user_id` ". 13 | "GROUP BY `user_id` ". 14 | "ORDER BY `written_formulas` DESC"; 15 | $stmt = $pdo->prepare($sql); 16 | $stmt->execute(); 17 | $users_by_raw_data = $stmt->fetchAll(); 18 | 19 | $sql = "SELECT COUNT(`id`) as `written_formulas_total` ". 20 | "FROM `wm_raw_draw_data`"; 21 | $stmt = $pdo->prepare($sql); 22 | $stmt->execute(); 23 | $written_formulas_total = $stmt->fetchObject()->written_formulas_total; 24 | 25 | $sql = "SELECT COUNT(`id`) as `written_formulas_total`, ". 26 | "COUNT(DISTINCT `accepted_formula_id`) as `distinct_symbols_total`". 27 | "FROM `wm_raw_draw_data`"; 28 | $stmt = $pdo->prepare($sql); 29 | $stmt->execute(); 30 | $row = $stmt->fetchObject(); 31 | $written_formulas_total = $row->written_formulas_total; 32 | $distinct_symbols_total = $row->distinct_symbols_total; 33 | 34 | // Write data to cache 35 | $data = array(); 36 | $data['users_by_raw_data'] = $users_by_raw_data; 37 | $data['written_formulas_total'] = $written_formulas_total; 38 | $data['distinct_symbols_total'] = $distinct_symbols_total; 39 | 40 | $fp = fopen($cachedir.$datafile, 'w'); 41 | fwrite($fp, json_encode($data)); 42 | fclose($fp); 43 | } else { 44 | $msg[] = array("class" => "alert-info", 45 | "text" => "This is a cached version from ". 46 | date("d.m.Y", filemtime($cachedir.$datafile))); 47 | $data = file_get_contents($cachedir.$datafile); 48 | $data = json_decode($data, true); 49 | } 50 | 51 | 52 | echo $twig->render('ranking.twig', array('heading' => 'Ranking', 53 | 'file'=> "ranking", 54 | 'logged_in' => is_logged_in(), 55 | 'display_name' => $_SESSION['display_name'], 56 | 'msg' => $msg, 57 | 'users_by_raw_data' => $data['users_by_raw_data'], 58 | 'written_formulas_total' => $data['written_formulas_total'], 59 | 'distinct_symbols_total' => $data['distinct_symbols_total'] 60 | ) 61 | ); -------------------------------------------------------------------------------- /website/raw-data/.htaccess: -------------------------------------------------------------------------------- 1 | Options +Indexes 2 | IndexOptions +FancyIndexing 3 | # The following path has to be adjusted. It has to be an absolute path. 4 | # Testing: 5 | #ErrorDocument 404 /website/raw-data/cache-404.php 6 | # Online: 7 | ErrorDocument 404 /write-math/raw-data/cache-404.php -------------------------------------------------------------------------------- /website/raw-data/cache-404.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 11 | $stmt->bindParam(':did', $raw_data_id, PDO::PARAM_INT); 12 | $stmt->execute(); 13 | $obj = $stmt->fetchObject(); 14 | if ($obj) { 15 | $data = $obj->data; 16 | $segmentation = $obj->segmentation; 17 | create_raw_data_svg($raw_data_id, $data, json_decode($segmentation)); 18 | header("Location: ../raw-data/$raw_data_id.svg"); 19 | } else { 20 | echo "Error - cache-404 for raw_data_id=".$raw_data_id; 21 | var_dump($obj); 22 | } 23 | 24 | ?> -------------------------------------------------------------------------------- /website/receive.php: -------------------------------------------------------------------------------- 1 | t) { 34 | $last_time = $point->t; 35 | } else { 36 | echo "Time has to be strictly increasing"; 37 | $is_valid = false; 38 | break 2; 39 | } 40 | } else { 41 | echo "A time value 't' has to exist."; 42 | $is_valid = false; 43 | break 2; 44 | } 45 | 46 | if(array_key_exists("x", $point) && array_key_exists("y", $point)) { 47 | if ($point->x < 0 || $point->y < 0) { 48 | echo "Both, 'x' and 'y' have to be non-negative."; 49 | $is_valid = false; 50 | break 2; 51 | } 52 | 53 | if ($point->x >= 268435456 || $point->y >= 268435456) { 54 | echo "Both, 'x' and 'y' have to be strictly smaller than 268435456."; 55 | $is_valid = false; 56 | break 2; 57 | } 58 | } else { 59 | echo "A 'x' and a 'y' value have to exist."; 60 | $is_valid = false; 61 | break 2; 62 | } 63 | } 64 | } 65 | return $data; 66 | } 67 | 68 | /** 69 | * Get an ID for the current session. 70 | * @return int Session ID 71 | */ 72 | function get_session_id() { 73 | //TODO 74 | return 0; 75 | } 76 | 77 | /** 78 | * Insert data into the database. 79 | * @param list $data representation of handdrawn image in JSON format 80 | * @param int $session_id 81 | * @param int $user_id 82 | * @return [type] [description] 83 | */ 84 | function insert_data($data, $user_id, $session_id=0) { 85 | if($session_id==0) { 86 | //TODO: Prefix-check 87 | //TODO: insert 88 | } else { 89 | //TODO: update 90 | } 91 | } 92 | 93 | if(isset($_GET['data'])) { 94 | $session_id = get_session_id(); 95 | 96 | $data = parse_data($_GET['data']); 97 | insert_data(json_encode($data), $session_id); 98 | echo "OK"; 99 | } 100 | ?> -------------------------------------------------------------------------------- /website/register/index.php: -------------------------------------------------------------------------------- 1 | "alert-warning", 24 | "text" => "There is already a user with ". 25 | "email '".$email."'."); 26 | } elseif(!$accept_terms) { 27 | $msg[] = array("class" => "alert-warning", 28 | "text" => "You have to accept the terms."); 29 | } else { 30 | $return = create_new_user($display_name, $email, $pw); 31 | $user_id = $return["id"]; 32 | $code = $return["confirmation_code"]; 33 | 34 | $message = "Hello ".$display_name."\n\n". 35 | "You can activate your account for http://write-math.com ". 36 | "with the following link:\n". 37 | "http://write-math.com/register/?email=$email&code=$code\n\n". 38 | "Please note that I will use and publish everything ". 39 | "you enter except your email address and your password. ". 40 | "As I share everything (for free) entering data cannot be ". 41 | "removed.\n\n". 42 | "Best regards,\n". 43 | "Martin Thoma"; 44 | $headers = 'From: Martin Thoma ' . PHP_EOL . 45 | 'Reply-To: Martin Thoma ' . PHP_EOL . 46 | 'X-Mailer: PHP/' . phpversion(); 47 | mail($email, "[Write-Math] Confirm account creation", $message, 48 | $headers); 49 | $msg[] = array("class" => "alert-success", 50 | "text" => "Your account has been created. An ". 51 | "activation Email was sent to the given ". 52 | "address."); 53 | } 54 | } 55 | 56 | /* confirm email */ 57 | if (isset($_GET['email']) && isset($_GET['code'])) { 58 | $return = activate_user($_GET['email'], $_GET['code']); 59 | if ($return) { 60 | $msg[] = array("class" => "alert-success", 61 | "text" => "Congratulations. Your account was activated."); 62 | } else { 63 | $msg[] = array("class" => "alert-warning", 64 | "text" => "This account could not be activated."); 65 | } 66 | } 67 | 68 | echo $twig->render('register.twig', array('heading' => 'Register', 69 | 'logged_in' => is_logged_in(), 70 | 'display_name' => $_SESSION['display_name'], 71 | 'file'=> "register", 72 | 'msg' => $msg, 73 | 'accepted_terms' => $accepted_terms, 74 | 'email' => $email, 75 | 'password_plain' => $_POST['password'] 76 | ) 77 | ); 78 | ?> -------------------------------------------------------------------------------- /website/similarity/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 11 | $stmt->bindParam(':base_symbol', $_POST['base_symbol_id'], PDO::PARAM_INT); 12 | $stmt->bindParam(':similar_symbol', $_POST['similar_symbol_id'], PDO::PARAM_INT); 13 | $stmt->bindParam(':comment', $_POST['description'], PDO::PARAM_STR); 14 | $stmt->execute(); 15 | } 16 | 17 | $sql = "SELECT `base_symbol`, `similar_symbol`, `comment_choice` ". 18 | "FROM `wm_similarity` ". 19 | "ORDER BY `base_symbol` ASC"; 20 | $stmt = $pdo->prepare($sql); 21 | $stmt->execute(); 22 | $symbols = $stmt->fetchAll(); 23 | 24 | echo $twig->render('similarity.twig', array('heading' => 'Symbol similarity', 25 | 'file'=> "similarity", 26 | 'logged_in' => is_logged_in(), 27 | 'display_name' => $_SESSION['display_name'], 28 | 'msg' => $msg, 29 | 'symbols' => $symbols 30 | ) 31 | ); -------------------------------------------------------------------------------- /website/stats/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 8 | $stmt->execute(); 9 | $tags = $stmt->fetchAll(); 10 | $tag_data = array(); 11 | foreach ($tags as $value) { 12 | $tag_data[$value['id']] = array('tag_name' => $value['tag_name'], 13 | 'is_package' => $value['is_package']); 14 | } 15 | 16 | $sql = "SELECT `tag_id`, `symbol_id` ". 17 | "FROM `wm_tags2symbols` "; 18 | $stmt = $pdo->prepare($sql); 19 | $stmt->execute(); 20 | $tags2symbol_fetched = $stmt->fetchAll(); 21 | $symbol2tags = array(); 22 | foreach ($tags2symbol_fetched as $value) { 23 | $symbol2tags[$value['symbol_id']][] = $value['tag_id']; 24 | } 25 | 26 | 27 | $sql = "SELECT `wm_formula`.`id`, `formula_in_latex`, `formula_name`, ". 28 | "`best_rendering`, `variant_of`, ". 29 | "COUNT(`wm_formula`.`id`) AS `counter` ". 30 | "FROM `wm_raw_draw_data` ". 31 | "JOIN `wm_formula` ON `wm_formula`.`id` = `accepted_formula_id` ". 32 | "WHERE (`formula_type` = 'single symbol' OR ". 33 | "`formula_type` = 'drawing' OR `formula_type` = 'nesting symbol') ". 34 | "GROUP BY `accepted_formula_id` ". 35 | "ORDER BY counter DESC"; 36 | $stmt = $pdo->prepare($sql); 37 | $stmt->execute(); 38 | $symbol_training_data_count = $stmt->fetchAll(); 39 | 40 | $sql = "SELECT `wm_formula`.`id`, COUNT(`symbol_id`) as `paper_count` ". 41 | "FROM `wm_formula` ". 42 | "JOIN `wm_formula_in_paper` ON `wm_formula`.`id` = `symbol_id` ". 43 | "WHERE (`formula_type` = 'single symbol' OR ". 44 | "`formula_type` = 'drawing' OR `formula_type` = 'nesting symbol') ". 45 | "GROUP BY `symbol_id` "; 46 | $stmt = $pdo->prepare($sql); 47 | $stmt->execute(); 48 | $paper_count = $stmt->fetchAll(); 49 | $formulaid2count = array(); 50 | foreach ($paper_count as $key => $value) { 51 | $formulaid2count[$value['id']] = $value['paper_count']; 52 | } 53 | 54 | $sum = 0; 55 | $total_references = 0; 56 | $important_count = 0; 57 | $important_count_raw = 0; 58 | $tmp = array(); 59 | foreach ($symbol_training_data_count as $key=>$s) { 60 | $sum += $s['counter']; 61 | if (array_key_exists($s['id'], $formulaid2count)) { 62 | $symbol_training_data_count[$key]['used_by_counter'] = $formulaid2count[$s['id']]; 63 | } else { 64 | $symbol_training_data_count[$key]['used_by_counter'] = 0; 65 | } 66 | $total_references += $symbol_training_data_count[$key]['used_by_counter']; 67 | } 68 | 69 | echo $twig->render('stats.twig', array('heading' => 'Stats', 70 | 'file'=> "stats", 71 | 'logged_in' => is_logged_in(), 72 | 'user_id' => get_uid(), 73 | 'display_name' => $_SESSION['display_name'], 74 | 'msg' => $msg, 75 | 'symbol_training_data_count' => $symbol_training_data_count, 76 | 'sum' => $sum, 77 | 'total_references' => $total_references, 78 | 'symbol2tags' => $symbol2tags, 79 | 'tag_data' => $tag_data 80 | ) 81 | ); -------------------------------------------------------------------------------- /website/symbol-identification/index.php: -------------------------------------------------------------------------------- 1 | render('symbol-identification.twig', array('heading' => 'Symbol Identification', 5 | 'file'=> "about", 6 | 'logged_in' => is_logged_in(), 7 | 'display_name' => $_SESSION['display_name'], 8 | 'msg' => $msg, 9 | ) 10 | ); -------------------------------------------------------------------------------- /website/tags/.htaccess: -------------------------------------------------------------------------------- 1 | Options -MultiViews 2 | RewriteEngine On 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /website/tags/index.php: -------------------------------------------------------------------------------- 1 | 0) { 19 | $sql = "SELECT `id`, `tag_name`, `description` ". 20 | "FROM `wm_tags` WHERE `tag_name`=:tag_name LIMIT 1"; 21 | $stmt = $pdo->prepare($sql); 22 | $stmt->bindParam(':tag_name', $tag_name, PDO::PARAM_STR); 23 | $stmt->execute(); 24 | $tag_info = $stmt->fetch(); 25 | 26 | // Get all symbols 27 | $sql = "SELECT * ". 28 | "FROM `wm_tags2symbols` ". 29 | "JOIN `wm_formula` ON `wm_formula`.`id` = `symbol_id` ". 30 | "WHERE `tag_id`=:tag_id ". 31 | "ORDER BY `unicode_dec`, `variant_of`, `wm_formula`.`id` ASC"; 32 | $stmt = $pdo->prepare($sql); 33 | $stmt->bindParam(':tag_id', $tag_info['id'], PDO::PARAM_STR); 34 | $stmt->execute(); 35 | $symbols = $stmt->fetchAll(); 36 | 37 | $Parsedown = new Parsedown(); 38 | $tag_info['description'] = $Parsedown->text($tag_info['description']); 39 | 40 | echo $twig->render('tag.twig', array('heading' => 'Tag: '.$tag_name.' ('.count($symbols).' symbols)', 41 | 'logged_in' => is_logged_in(), 42 | 'display_name' => $_SESSION['display_name'], 43 | 'file'=> "tag", 44 | 'tag_info' => $tag_info, 45 | 'symbols' => $symbols, 46 | ) 47 | ); 48 | } else { 49 | // Get all tags 50 | $sql = "SELECT * ". 51 | "FROM `wm_tags` ". 52 | "ORDER BY `tag_name` ASC"; 53 | $stmt = $pdo->prepare($sql); 54 | $stmt->execute(); 55 | $tags = $stmt->fetchAll(); 56 | echo $twig->render('tags_overview.twig', 57 | array('heading' => 'Tags ('.count($tags).')', 58 | 'logged_in' => is_logged_in(), 59 | 'display_name' => $_SESSION['display_name'], 60 | 'file'=> "tag", 61 | 'tags' => $tags, 62 | ) 63 | ); 64 | } 65 | 66 | 67 | ?> -------------------------------------------------------------------------------- /website/templates/about.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | {% block header %} 3 | 38 | {% endblock %} 39 | {% block content %} 40 |

This is project part of the bachelor thesis of 41 | Martin Thoma. 42 | All source files are on 43 | GitHub.

44 | 45 |

If you want to identify a single symbol, I've added some hints 46 | here.

47 | 48 |

You can file issues / bugs in the 49 | Issue Tracker 50 | or alternatively send me an email. You can contact me via info@martin-thoma.de.

51 | 52 |

Technical and Scientific Information

53 |

If you want to know more about how write-math.com works from a technical / 54 | scientific perspective, please go to martin-thoma.com/write-math

55 | 56 |

Statistics

57 |
    58 |
  • {{ user_count }} users
  • 59 |
  • {{ formula_count }} formulas (that includes simple symbols)
  • 60 |
  • {{ raw_data_count}} recordings.
    61 | {{ unclassified_count }} of those are not classified yet.
  • 62 |
  • Symbols by number of trainings instances.
  • 63 |
64 | 65 |

Credits

66 |

This website was / is mainly developed by me (Martin Thoma), but many people 67 | contributed to it. Here are some of them:

68 | 69 |
    70 |
  • Prof. Waibel allowed me to write my bachelors thesis about this topic 71 | and later gave me a job in which I can continue developing it.
  • 72 |
  • Kevin Kilgour and Dr. Sebastian Stücker gave me advice for my 73 | bachelors thesis. They introduced me to neural networks.
  • 74 |
  • Daniel Kirsch gave me lots of data from detexify.
  • 75 |
  • David Carlisle gave me plenty of advice at 76 | chat.stackexchange.com. 77 | He also published unicode.xml, 78 | so that I can use unicode descriptions for the symbols.
  • 79 |
80 | 81 | Thank you for helping me to build this service! 82 | 83 |

Used Open Content

84 | 85 |
    86 |
  • Detexify Data: Licensed under ODbL.
  • 87 |
  • MfrDB: Not licensed, but one of the authors gave the permission to use the data.
  • 88 |
  • STIX font: Licensed under SIL.
  • 89 |
90 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/compare.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block header %} 4 | 5 | 32 | {% endblock %} 33 | 34 | {% block content %} 35 |
36 |
37 |
38 |

A

39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 |

B

48 |
49 |
50 | 51 |
52 |
53 |

54 |
55 |
56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 |
DTW distance{{ dtw_distance }}
70 |
71 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/forgot.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | {% if password_changed %} 5 |

Login

6 | {% else %} 7 |
8 |
9 | 10 | 13 |
14 | 15 | 16 | {% endif %} 17 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/gallery.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | {% import "pagination.twig" as pagination %} 3 | 4 | {% block content %} 5 | 12 | 13 | 27 | 28 | {{ pagination.pagination(currentPage, '&tab=' ~ tab, total, pages, 5) }} 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/login.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | {% if logged_in and account_type != 'IP-User' %} 5 |
You are already logged in as 6 | '{{ display_name }}' with email '{{ email }}'. 7 |
8 | {% else %} 9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 | 29 |

You don't have an account? Please register.

30 | {% endif %} 31 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/pagination.twig: -------------------------------------------------------------------------------- 1 | {% macro pagination(currentPage, append_information, total, pages, delta) %} 2 | {% if total==0%} 3 |

No items

4 | {% else %} 5 |
    6 | {% if pages >= 2*delta %} 7 |
  • 1
  • 8 | {% endif %} 9 | {% if currentPage > 1 %} 10 |
  • «
  • 11 | {% else %} 12 |
  • «
  • 13 | {% endif %} 14 | 15 | {% set leftdots = false %} 16 | {% set rightdots = false %} 17 | {% for i in 1..(pages) %} 18 | {% if (0<=i-currentPage and i-currentPage <= delta) or (0<= currentPage-i and currentPage-i <= delta) %} 19 |
  • {{ i }}
  • 20 | {% elseif (0 <= currentPage-i) and (leftdots == false)%} 21 |
  • ...
  • 22 | {% set leftdots = true %} 23 | {% elseif (0 <= i-currentPage) and (rightdots == false)%} 24 |
  • ...
  • 25 | {% set rightdots = true %} 26 | {% endif %} 27 | {% endfor %} 28 | 29 | {% if currentPage < total %} 30 |
  • »
  • 31 | {% else %} 32 |
  • »
  • 33 | {% endif %} 34 | {% if pages >= 2*delta %} 35 |
  • {{ (pages+1) }}
  • 36 | {% endif %} 37 |
38 | {% endif %} 39 | {% endmacro %} -------------------------------------------------------------------------------- /website/templates/problems.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | {% import "pagination.twig" as pagination %} 3 | 4 | {% block content %} 5 |
6 |
7 | 8 | 14 |
15 |
16 | 21 |
22 |
23 | 28 |
29 |
30 | 35 |
36 |
37 | 42 |
43 |
44 | 49 |
50 |
51 | 56 |
57 | 58 |
59 | 60 | 74 | 75 | {{ pagination.pagination(currentPage, pagination_url, total, pages, 5) }} 76 | 77 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/ranking.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | 5 |
Only users with at least 5 written formulas will be 6 | listed below.
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for user in users_by_raw_data %} 16 | {% if user.written_formulas >= 5 %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endif %} 24 | {% endfor %} 25 | 26 | 27 | 28 | 29 | 30 | 31 |
#UserWritten formulasDistinct symbols
{{ loop.index }}{{ user.display_name }}{{ user.written_formulas }}{{ user.distinct_symbols }}
#Total{{ written_formulas_total }}{{ distinct_symbols_total }}
32 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/recordingjs.twig: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /website/templates/register.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 7 |
8 | 13 |

This email address will be used to 14 | help you recover your password in case you forgot it. 15 | It will not be shared with anybody and only be stored 16 | on this websites server.
17 | You could use mailinator, 18 | a disposable email address service if you're still worried 19 | about confidentiality of your email address.

20 |
21 |
22 |
23 | 24 |
25 | 30 |

Your password will be stored hashed and 31 | individually salted. This means we don't store your password itself, 32 | but an encrypted version of it. This encrypted version will not 33 | be published. See cryptographic hash function for a more detailed explanation.

34 |
35 |
36 | 37 |
38 | 45 |
46 | Accept that every information you enter 47 | (except your email address and password) will be completely shared 48 | with the public. This sharing with the public can probably not be 49 | undone, because other people might have copied the information. 50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/search.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 |
5 | 6 |

You might want to have a look at the tags. You can use them in the search with the syntax [arrow]. You can also browse symbols by unicode search.

7 |
8 |
9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | {% if searchterm != '' %} 18 | {% if searchresults|length == 0 %} 19 | 20 | {% else %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for r in searchresults %} 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | {% endfor %} 42 |
#Rendering NameLaTeX
{{ loop.index }}Rendering of {{ r.formula_name }} 34 | {% if r.unicode_dec != 0 %} 35 | &#{{r.unicode_dec|raw}}; 36 | {% endif %} 37 | {{ r.formula_name }}{{ r.formula_in_latex }}
43 | {% endif %} 44 | {% endif %} 45 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/similarity.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 |

Some symbols can only be destingushed by context.

5 | 6 |

Positive examples for this phenomenon are below. Most well-known might 7 | be \Sigma and \sum.

8 | 9 |

Negative examples are:

10 | 11 |
    12 |
  • s and S
  • 13 |
  • 0 and O (some people make a slash through the zero)
  • 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for symbol in symbols %} 25 | 26 | 27 | 30 | 33 | 34 | {% endfor %} 35 |
#Base symbolSimilar symbolComment
{{ loop.index }}{{ symbol.comment_choice }}
36 | 37 | 38 |
39 |
40 | 41 | 44 |
45 |
46 | 47 | 51 |
52 |
53 | 54 | 55 |

Explain why you think the base symbol should be the base (and not the similar).

56 |
57 | 58 | 59 |
60 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/stats.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | Not all of the following symbols can be recognized by the current system. An 5 | important subset of almost 400 mathematical symbols was chosen. The other 6 | symbols were either used in another context or had to little training data to 7 | guarantee good recognition results. A list of the recognizable symbols can 8 | be found at 9 | hwrt/misc/symbols.yml. 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for s in symbol_training_data_count %} 30 | 31 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | {% endfor %} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
#Formula in LaTeXvarInstances in databaseref
{{ loop.index }}{{s.formula_in_latex}} 35 | {% for tagid in symbol2tags[s.id] %} 36 | {{tagid2name[tagid]}} 37 | {% endfor %} 38 | {% if s.variant_of|length>0%}{{s.variant_of}}{% endif %}{{s.counter}}{{s.used_by_counter}}
   {{ sum }}{{ total_references }}
53 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/svg-problems.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | {% block content %} 3 |

{{ problematic_formulas|length }} formulas are problematic.

4 | {% for formula in problematic_formulas %} 5 |
  • {{ formula.formula_id }}: {{ formula.problem_type }} 6 | {% if is_admin %} 7 | - Formula is ok 8 | {% endif %} 9 |
  • 10 | {% endfor %} 11 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/symbol-identification.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | How to look up a symbol or identify a math symbol or character? 5 | gives great answers for single symbol identification. This is only a short 6 | list of the common ones I use. If you can't find it, you can use tex.stackexchange.com 7 | to ask experts for it. 8 | 9 |

    By on-line drawing

    10 | 14 | 15 |

    By Text

    16 | 21 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/tag.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block header %} 4 | 18 | {% endblock %} 19 | 20 | {% block content %} 21 |

    {{ tag_info.description |raw }}

    22 | 23 | 39 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/tags_overview.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block header %} 4 | 18 | {% endblock %} 19 | 20 | {% block content %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for tag in tags %} 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 | 36 |
    Tag NameDescription
    {{ tag.tag_name }}{{ tag.description }}
    37 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/unicode.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block content %} 4 | 5 | 19 | 20 |

    Red background means there is no entry in the write-math database. 21 | Yellow entry means there are multiple ones.

    22 | 23 |

    Blocks: 24 | ASCII 25 | Cyrillic 26 | arrows 27 | Supplemental Arrows-A 28 | Supplemental Arrows-B 29 | Supplemental Arrows-C 30 | Miscellaneous Symbols and Arrows 31 | Mathematical Latin Letters 32 | Mathematical Greek Letters 33 | Mathematical Digits 34 | and many more. 35 |

    36 | 37 |
    38 |
    39 | 40 |
    41 | 46 |
    47 |
    48 |
    49 | 50 |
    51 | 56 |
    57 |
    58 |
    59 |
    60 | 61 |
    62 |
    63 |
    64 | 65 |
    66 | {% if searchresults|length>0 %} 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {% for unicode, ru in searchresults %} 77 | {% for unicode, r in ru %} 78 | {% if r.best_rendering %} 79 | 80 | 81 | 82 | 87 | 88 | 89 | 90 | 91 | {% else %} 92 | 93 | 94 | 95 | 100 | 101 | 102 | 103 | 104 | {% endif %} 105 | {% endfor %} 106 | {% endfor %} 107 |
    #Rendering NameLaTeXLink
    1 %}class="y"{%endif%}>{{ r.unicode_dec }}Rendering of {{ r.formula_name }} 83 | {% if r.unicode_dec != 0 %} 84 | &#{{r.unicode_dec|raw}}; 85 | {% endif %} 86 | {{ r.formula_name }}{{ r.formula_in_latex }}Link
    {{ r.unicode_dec }}  96 | {% if r.unicode_dec != 0 %} 97 | &#{{r.unicode_dec|raw}}; 98 | {% endif %} 99 | {{ r.formula_name }} Link
    108 | {% endif %} 109 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/user.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | {% import "pagination.twig" as pagination %} 3 | 4 | {% block content %} 5 | {% if user %} 6 |
    7 |
    8 |
    9 |
    10 |
    11 |
    {{ user.description|raw }}
    12 |
    13 |
    14 | 15 |
    16 |
    17 |
    18 | 19 |
    20 | 34 |
    35 |
    36 | 37 |
    38 | 39 |
    40 | 55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 | 62 | 79 | 80 | {{ pagination.pagination(currentPage, '&id=' ~ user.id, total, pages, 5) }} 81 | {% else %} 82 |

    This user seems not to exist.

    83 | {% endif %} 84 | {% endblock %} -------------------------------------------------------------------------------- /website/templates/worker.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    Worker{{ worker.worker_name }}
    Description{{ worker.description }}
    URL{{ worker.url }}
    Latest Heartbeat{{ worker.latest_heartbeat }}
    Created by{{ worker.display_name }}
    27 | {% endblock %} -------------------------------------------------------------------------------- /website/train/functions.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 12 | $stmt->bindParam(':data', $data, PDO::PARAM_STR); 13 | $stmt->execute(); 14 | $raw_data_set = $stmt->fetchObject(); 15 | if ($raw_data_set === False) { 16 | $sql = "INSERT INTO `wm_raw_draw_data` (". 17 | "`user_id`, ". 18 | "`data`, ". 19 | "`md5data`, ". 20 | "`creation_date`, ". 21 | "`user_agent`, ". 22 | "`accepted_formula_id`". 23 | ") VALUES (:uid, :data, MD5(:data), ". 24 | "CURRENT_TIMESTAMP, :user_agent, :formula_id);"; 25 | $stmt = $pdo->prepare($sql); 26 | $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); 27 | $stmt->bindParam(':data', $data, PDO::PARAM_STR); 28 | $stmt->bindParam(':formula_id', $formula_id, PDO::PARAM_INT); 29 | $stmt->bindParam(':user_agent', $_SERVER['HTTP_USER_AGENT'], PDO::PARAM_STR); 30 | $stmt->execute(); 31 | $raw_data_id = $pdo->lastInsertId('id'); 32 | 33 | $sql = "INSERT INTO `wm_partial_answer` (". 34 | "`recording_id` ,". 35 | "`symbol_id` ,". 36 | "`user_id`, ". 37 | "`strokes` ". 38 | ") VALUES (". 39 | ":raw_data_id, :formula_id, :uid, :strokes);"; 40 | $stmt = $pdo->prepare($sql); 41 | $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); 42 | $stmt->bindParam(':raw_data_id', $raw_data_id, PDO::PARAM_INT); 43 | $stmt->bindParam(':formula_id', $formula_id, PDO::PARAM_INT); 44 | $total_strokes = count(json_decode($data)); 45 | $strokes = implode(',', range(0, $total_strokes-1)); 46 | $stmt->bindParam(':strokes', $strokes, PDO::PARAM_INT); 47 | $stmt->execute(); 48 | } else { 49 | $msg[] = array("class" => "alert-warning", 50 | "text" => "You've already submitted this data. ". 51 | "Please wait 5 seconds. This time is ". 52 | "needed to compare your recording with ". 53 | "4000 others."); 54 | } 55 | } else { 56 | $msg[] = array("class" => "alert-danger", 57 | "text" => "This could not be inserted. It didn't even ". 58 | "have a single point. You sent:
    ". 59 | "
    ".$data."
    ". 60 | "At the moment I have problems with single ". 61 | "points. This might be a symptom of those ". 62 | "problems. See issue 6."); 63 | return false; 64 | } 65 | 66 | } 67 | 68 | ?> -------------------------------------------------------------------------------- /website/unicode/README.md: -------------------------------------------------------------------------------- 1 | Thank you David Carlisle for https://github.com/w3c/xml-entities -------------------------------------------------------------------------------- /website/unicode/create_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Create a json file which maps unicode decimal codepoints to descriptions. 5 | 6 | https://github.com/w3c/xml-entities is used for that. 7 | """ 8 | 9 | import json 10 | 11 | data = {} 12 | 13 | import xml.etree.ElementTree 14 | e = xml.etree.ElementTree.parse('xml-entities/unicode.xml').getroot() 15 | for atype in e.findall('charlist'): 16 | print("## Charlist found") 17 | for character in atype.findall('character'): 18 | try: 19 | dec = int(character.get('dec')) 20 | desc = '' 21 | for description in character.findall('description'): 22 | desc = description.text 23 | # print("%s: - %s" % (dec, desc)) 24 | data[dec] = desc 25 | except: 26 | # Just ignore errors 27 | pass 28 | 29 | with open('unicode.json', 'w') as outfile: 30 | json.dump(data, outfile, sort_keys=True, indent=1) 31 | -------------------------------------------------------------------------------- /website/unicode/index.php: -------------------------------------------------------------------------------- 1 | 2000) { 13 | $max_unicode = $min_unicode + 2000; 14 | } 15 | 16 | 17 | $sql = "SELECT `wm_formula`.id, best_rendering, formula_name, unicode_dec, font, ". 18 | "font_style, formula_in_latex ". 19 | "FROM `wm_formula` "; 20 | $sql .= "WHERE (:min_unicode <= `unicode_dec` AND `unicode_dec` <= :max_unicode) "; 21 | $sql .= "ORDER BY `unicode_dec`, `formula_name` ASC"; 22 | $stmt = $pdo->prepare($sql); 23 | $stmt->execute(array(':min_unicode' => $min_unicode, 24 | ':max_unicode' => $max_unicode)); 25 | $searchresults = $stmt->fetchAll(); 26 | 27 | $res = array(); 28 | for ($unicode=$min_unicode; $unicode <= $max_unicode; $unicode++) { 29 | $res[$unicode] = array(); 30 | } 31 | 32 | # Fill array with searched values 33 | foreach ($searchresults as $key => $value) { 34 | $value['base16'] = base_convert($value['unicode_dec'], 10, 16); 35 | $res[$value['unicode_dec']][] = $value; 36 | } 37 | 38 | $searchresults = $res; 39 | 40 | # Read the unicode.json 41 | $string = file_get_contents("unicode.json"); 42 | $json_a = json_decode($string, true); 43 | 44 | # Go through unicode website, if no result was found 45 | for ($unicode=$min_unicode; $unicode <= $max_unicode; $unicode++) { 46 | if (count($searchresults[$unicode]) == 0) { 47 | $base16 = base_convert($unicode, 10, 16); 48 | $searchresults[$unicode][] = array('unicode_dec' => $unicode, 49 | 'font' => 'STIXGeneral', 50 | 'font_style' => 'normal', 51 | 'formula_name' => $json_a[$unicode], 52 | 'base16' => $base16); 53 | } 54 | } 55 | } 56 | 57 | 58 | echo $twig->render('unicode.twig', array('heading' => 'Unicode', 59 | 'logged_in' => is_logged_in(), 60 | 'display_name' => $_SESSION['display_name'], 61 | 'file'=> "search", 62 | 'searchresults' => $searchresults, 63 | 'min_unicode' => $min_unicode, 64 | 'max_unicode' => $max_unicode 65 | ) 66 | ); 67 | 68 | ?> -------------------------------------------------------------------------------- /website/user.func.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 7 | $stmt->bindParam(':display_name', $display_name, PDO::PARAM_STR); 8 | $stmt->execute(); 9 | $row = $stmt->fetchObject(); 10 | return !($row->id == 0); 11 | } 12 | 13 | function does_email_exist($email) { 14 | global $pdo; 15 | 16 | $sql = "SELECT `id` FROM `wm_users` WHERE `email` = :email"; 17 | $stmt = $pdo->prepare($sql); 18 | $stmt->bindParam(':email', $email, PDO::PARAM_STR); 19 | $stmt->execute(); 20 | $row = $stmt->fetchObject(); 21 | return !($row->id == 0); 22 | } 23 | 24 | function generate_display_name() { 25 | $prefix = "user_"; 26 | $i = rand(); 27 | return $prefix.$i; 28 | } 29 | 30 | function create_new_user($display_name, $email, $pw, 31 | $account_type='Regular User') { 32 | global $pdo; 33 | 34 | $sql = "INSERT INTO `wm_users` (". 35 | "`display_name` ,". 36 | "`email` ,". 37 | "`password`, ". 38 | "`confirmation_code`, ". 39 | "`status`, ". 40 | "`account_type` ". 41 | ") VALUES (". 42 | ":display_name, :email, :password, ". 43 | ":confirmation_code, 'deactivated', :account_type);"; 44 | $stmt = $pdo->prepare($sql); 45 | $stmt->bindParam(':display_name', $display_name, PDO::PARAM_STR); 46 | $stmt->bindParam(':email', $email, PDO::PARAM_STR); 47 | $hash = password_hash($pw, PASSWORD_BCRYPT, array("cost" => 10)); 48 | $stmt->bindParam(':password', $hash, PDO::PARAM_STR); 49 | $stmt->bindParam(':account_type', $account_type, PDO::PARAM_STR); 50 | $code = md5(rand()); 51 | $stmt->bindParam(':confirmation_code', $code, PDO::PARAM_STR); 52 | $stmt->execute(); 53 | 54 | return array("id" => $pdo->lastInsertId(), "confirmation_code" => $code); 55 | } 56 | 57 | function activate_user($email, $code) { 58 | global $pdo; 59 | $sql = "UPDATE `wm_users` ". 60 | "SET status = 'activated' ". 61 | "WHERE `email` = :email AND `confirmation_code` = :code"; 62 | $stmt = $pdo->prepare($sql); 63 | $stmt->bindParam(':code', $code, PDO::PARAM_STR); 64 | $stmt->bindParam(':email', $email, PDO::PARAM_STR); 65 | 66 | $stmt->execute(); 67 | if ($stmt->rowCount() == 1) { 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | ?> -------------------------------------------------------------------------------- /website/user/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 9 | $stmt->bindParam(':uid', $_GET['id'], PDO::PARAM_INT); 10 | $stmt->execute(); 11 | $user =$stmt->fetchObject(); 12 | 13 | if ($user !== false) { 14 | $sql = "SELECT `language_code` , `english_language_name` 15 | FROM `wm_languages` 16 | ORDER BY `english_language_name` ASC"; 17 | $stmt = $pdo->prepare($sql); 18 | $stmt->execute(); 19 | $languages =$stmt->fetchAll(); 20 | 21 | // Get total number of elements for pagination 22 | $sql = "SELECT COUNT(`id`) as counter FROM `wm_raw_draw_data` ". 23 | "WHERE `user_id` = :uid"; 24 | $stmt = $pdo->prepare($sql); 25 | $uid = get_uid(); 26 | $stmt->bindParam(':uid', $_GET['id'], PDO::PARAM_STR); 27 | $stmt->execute(); 28 | $row = $stmt->fetchObject(); 29 | $total = $row->counter; 30 | 31 | // Get all raw data of this user 32 | $currentPage = isset($_GET['page']) ? intval($_GET['page']) : 1; 33 | $sql = "SELECT `id`, `data` as `image`, `creation_date`, ". 34 | "`accepted_formula_id` ". 35 | "FROM `wm_raw_draw_data` ". 36 | "WHERE `user_id` = :uid ". 37 | "ORDER BY `creation_date` DESC ". 38 | "LIMIT ".(($currentPage-1)*14).", 14"; 39 | $stmt = $pdo->prepare($sql); 40 | $uid = get_uid(); 41 | $stmt->bindParam(':uid', $_GET['id'], PDO::PARAM_STR); 42 | $stmt->execute(); 43 | $userimages = $stmt->fetchAll(); 44 | 45 | $Parsedown = new Parsedown(); 46 | $user->description = $Parsedown->text($user->description); 47 | 48 | echo $twig->render('user.twig', array('heading' => 'User \''.$user->display_name.'\'', 49 | 'file' => "user", 50 | 'logged_in' => is_logged_in(), 51 | 'display_name' => $_SESSION['display_name'], 52 | 'gravatar' => "http://www.gravatar.com/avatar/".md5($user->email), 53 | 'user' => $user, 54 | 'languages' => $languages, 55 | 'userimages' => $userimages, 56 | 'total' => $total, 57 | 'pages' => ceil(($total)/14), 58 | 'currentPage' => $currentPage 59 | ) 60 | ); 61 | } else { 62 | echo $twig->render('user.twig', array('heading' => 'User unknown', 63 | 'file' => "user", 64 | 'logged_in' => is_logged_in(), 65 | 'display_name' => $_SESSION['display_name'] 66 | ) 67 | ); 68 | } 69 | 70 | 71 | 72 | ?> -------------------------------------------------------------------------------- /website/view/functions.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 13 | $stmt->execute(); 14 | $tags = $stmt->fetchAll(); 15 | $tagsById = array(); 16 | foreach ($tags as $tag) { 17 | $tagsById[$tag['id']] = array("tag_name" => $tag['tag_name'], 18 | "is_package" => $tag['is_package']); 19 | } 20 | return $tagsById; 21 | } 22 | 23 | function addTagIds($answers, $tagsById) { 24 | global $pdo; 25 | for($i = 0; $i < count($answers); $i++) { 26 | $symbol_id = $answers[$i]['symbol_id']; 27 | $answers[$i]['tag_ids'] = array(); 28 | $answers[$i]['packages'] = array(); 29 | // Get tag IDs of this answer 30 | $sql = "SELECT `tag_id` ". 31 | "FROM `wm_tags2symbols` ". 32 | "WHERE `symbol_id` = :symbol_id"; 33 | $stmt = $pdo->prepare($sql); 34 | $stmt->bindParam(':symbol_id', $symbol_id, PDO::PARAM_INT); 35 | $stmt->execute(); 36 | $tag_ids = $stmt->fetchAll(); 37 | foreach ($tag_ids as $key => $value) { 38 | $answers[$i]['tag_ids'][] = $tagsById[$value['tag_id']]; 39 | if ($tagsById[$value['tag_id']]['is_package']) { 40 | $answers[$i]['packages'][] = $tagsById[$value['tag_id']]['tag_name']; 41 | } 42 | } 43 | } 44 | return $answers; 45 | } 46 | 47 | ?> -------------------------------------------------------------------------------- /website/view/submit_answer.php: -------------------------------------------------------------------------------- 1 | "alert-info", 18 | "text" => "LaTex was normalized from '$latex' to '$latex_new'."); 19 | $latex = $latex_new; 20 | } 21 | 22 | // Get formula id if it is already in the database 23 | $sql = "SELECT `id` FROM `wm_formula` ". 24 | "WHERE `formula_in_latex` = :latex"; 25 | $stmt = $pdo->prepare($sql); 26 | $stmt->bindParam(':latex', $latex, PDO::PARAM_STR); 27 | $stmt->execute(); 28 | $formula_id = $stmt->fetchObject()->id; 29 | 30 | if($formula_id == 0 || $formula_id == null) { 31 | // it was not in the database. Add it. 32 | $sql = "INSERT INTO `wm_formula` (". 33 | "`formula_name`, `formula_in_latex`, `mode`, `user_id` ". 34 | ") VALUES (:latex, :latex, :mode, :uid);"; 35 | $stmt = $pdo->prepare($sql); 36 | $latex = trim($latex); 37 | 38 | $stmt->bindParam(':latex', $latex, PDO::PARAM_STR); 39 | $mode = 'bothmodes'; 40 | $stmt->bindParam(':mode', $mode, PDO::PARAM_STR); 41 | $uid = get_uid(); 42 | $stmt->bindParam(':uid', $uid, PDO::PARAM_INT); 43 | $stmt->execute(); 44 | $formula_id = $pdo->lastInsertId('id'); 45 | } 46 | $ret = add_partial_classification_pure($user_id, $raw_data_id, $formula_id, $strokes); 47 | return '{"add_partial_classification_pure": "'.$ret.'",'. 48 | '"formula_id": "'.$formula_id.'"}'; 49 | } 50 | 51 | function add_partial_classification_pure($user_id, $raw_data_id, $formula_id, $strokes) { 52 | global $pdo; 53 | global $msg; 54 | 55 | $sql = "INSERT INTO `wm_partial_answer` (". 56 | "`user_id` ,". 57 | "`recording_id` ,". 58 | "`strokes`, ". 59 | "`symbol_id`". 60 | ") VALUES (". 61 | ":user_id, :raw_data_id, :strokes, :symbol_id". 62 | ");"; 63 | $stmt = $pdo->prepare($sql); 64 | $uid = get_uid(); 65 | $stmt->bindParam(':user_id', $uid, PDO::PARAM_INT); 66 | $stmt->bindParam(':raw_data_id', $raw_data_id, PDO::PARAM_INT); 67 | $stmt->bindParam(':strokes', $strokes, PDO::PARAM_STR); 68 | $stmt->bindParam(':symbol_id', $formula_id, PDO::PARAM_INT); 69 | try { 70 | $stmt->execute(); 71 | adjust_user_answer_count($raw_data_id, 1); 72 | } catch (PDOException $e) { 73 | if ($e->errorInfo[1] == 1062) { 74 | // duplicate entry, do something else 75 | return '{"error": "This classification does already exist."}'; 76 | } else { 77 | return '{"error": ".'.implode(":", $pdo->errorInfo()).'}'; 78 | } 79 | } 80 | return json_encode(1); 81 | } 82 | 83 | function filter_strokes($strokes, $total_strokes) { 84 | $strokes = explode(",", $strokes); 85 | $filtered_strokes = array(); 86 | foreach ($strokes as $stroke) { 87 | if (is_numeric($stroke)) { 88 | $stroke_nr = intval($stroke); 89 | if (0 <= $stroke_nr && $stroke_nr < $total_strokes) { 90 | $filtered_strokes[] = $stroke_nr; 91 | } 92 | } 93 | } 94 | return $filtered_strokes; 95 | } 96 | 97 | ?> -------------------------------------------------------------------------------- /website/worker/index.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 15 | $stmt->bindParam(':wid', $_GET['id'], PDO::PARAM_INT); 16 | $stmt->execute(); 17 | $worker = $stmt->fetchObject(); 18 | } 19 | 20 | echo $twig->render('worker.twig', array('heading' => 'Worker', 21 | 'file' => "worker", 22 | 'logged_in' => is_logged_in(), 23 | 'display_name' => $_SESSION['display_name'], 24 | 'user_id' => $user->id, 25 | 'worker' => $worker 26 | ) 27 | ); 28 | 29 | ?> -------------------------------------------------------------------------------- /website/write-math.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": ".", 7 | "folder_exclude_patterns": ["cache", "vendor", "css", "icons"], 8 | "file_exclude_patterns": ["*.lock", "formulas/*.svg", "*.png", "*.ico"] 9 | } 10 | ], 11 | "settings": 12 | { 13 | "tab_size": 4, 14 | "translate_tabs_to_spaces": true, 15 | "use_tab_stops": true, 16 | "detect_indentation": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /website/writemath.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # v2 - 2014-05-17 3 | 4 | # Explicitly cached 'master entries'. 5 | CACHE: 6 | icons/accept.png 7 | icons/cross.png 8 | icons/Crystal_Clear_action_apply.png 9 | icons/Crystal_Clear_action_apply-gray.png 10 | icons/pencil.png 11 | icons/offline.svg 12 | favicon.ico 13 | nice-highres.png 14 | niceicon.png 15 | css/style.css 16 | 17 | # Resources that require the user to be online. 18 | NETWORK: 19 | register/ 20 | login/ 21 | gallery/ 22 | train/ 23 | ranking/ 24 | classify/ 25 | about/ 26 | 27 | # static.html will be served if main.py is inaccessible 28 | # offline.jpg will be served in place of all images in images/large/ 29 | # offline.html will be served in place of all other .html files 30 | FALLBACK: 31 | *.svg icons/offline.svg 32 | http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css css/bootstrap.min.css -------------------------------------------------------------------------------- /writing-examples/README.md: -------------------------------------------------------------------------------- 1 | Different libraries to record data. -------------------------------------------------------------------------------- /writing-examples/easel-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EaselJS example 5 | 6 | 7 | 12 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /writing-examples/fabric-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple example 5 | 6 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /writing-examples/paper-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Paper.js example 5 | 6 | 11 | 12 | 13 | 41 | 42 | 43 | 44 | --------------------------------------------------------------------------------