{"id":17030518,"url":"https://github.com/vsoch/juliart-says","last_synced_at":"2026-05-06T10:39:58.335Z","repository":{"id":141667798,"uuid":"230664602","full_name":"vsoch/juliart-says","owner":"vsoch","description":"Generate text covered Julia Set images, either with sentences from a corpus or Markov generated content.","archived":false,"fork":false,"pushed_at":"2019-12-29T20:31:19.000Z","size":10186,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-06T13:29:45.643Z","etag":null,"topics":["juliart","markov","wordgrams"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vsoch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-28T20:42:43.000Z","updated_at":"2020-07-04T15:45:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"36f168ae-9213-4860-8fcb-db418f092bb2","html_url":"https://github.com/vsoch/juliart-says","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vsoch/juliart-says","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsoch%2Fjuliart-says","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsoch%2Fjuliart-says/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsoch%2Fjuliart-says/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsoch%2Fjuliart-says/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vsoch","download_url":"https://codeload.github.com/vsoch/juliart-says/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsoch%2Fjuliart-says/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32689673,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T08:33:17.875Z","status":"ssl_error","status_checked_at":"2026-05-06T08:33:17.221Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["juliart","markov","wordgrams"],"created_at":"2024-10-14T08:07:15.750Z","updated_at":"2026-05-06T10:39:58.299Z","avatar_url":"https://github.com/vsoch.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JuliaArt Says\n\nThis is a Juliart generator that creates a graphic with a snippet of wisdom\ngenerated via a Markov Model based on some [text corpus](corpus).\nWe use version 0.0.13 of [juliart](https://www.github.com/vsoch/juliart).\n\n![images/hamlet-pacifico.png](images/hamlet-pacifico.png)\n\n - [Usage](#usage): jump in and use the tool!\n   - [Defaults](#defaults): generate an image using defaults\n   - [Corpus](#corpus): specify a custom corpus\n   - [Font](#font): customize the font\n - [Markov Models](#markov-models): a little bit about how we generate the text\n - [Docker](#docker): build a container instead.\n - [Needs Love](#needs-love): what I'd like to improve\n\nYou can see from the image that the defaults print a text overlay on the image\nwith slight transparency, so it's akin to a background. You can customize\nthe many [juliart](https://github.com/vsoch/juliart) parameters to tweak\nthis behavior, and likely I'll work on a similar example that shows\nprinting a single quote.\n\n\n## Usage\n\nYou can see the basic usage as follows:\n\n```bash\nusage: juliasays.py [-h] {generate} ...\n\nJuliaSet Says: wisdom embedded in Julia Sets graphics\n\noptional arguments:\n  -h, --help  show this help message and exit\n\nactions:\n  actions for juliart-says generator\n\n  {generate}  juliart-says actions\n    generate  generate a juliart-says image\n```\n\nThe command of interest is \"generate\"\n\n```bash\n$ ./juliasays.py generate --help\nusage: juliasays.py generate [-h] [--radius RADIUS] [--outfile OUTFILE]\n                             [--fontsize FONTSIZE] [--xcoord XCOORD]\n                             [--ycoord YCOORD] [--ca CA] [--cb CB] [--res RES]\n                             [--iter ITERS] [--color {random,pattern,glow}]\n                             [--rgb RGB]\n                             [--theme {christmas,easter,fall,random,halloween,hanukkah,spring,summer,thanksgiving,valentine,winter}]\n                             [--corpus {trump,hamlet,dr_seuss,ts_eliot}]\n                             [--custom-corpus CUSTOM_CORPUS] [--no-model]\n                             [--zoom ZOOM] [--size SIZE] [--alpha ALPHA]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --radius RADIUS       the max radius to allow (default is 4)\n  --outfile OUTFILE     the output file to save the image (defaults to\n                        randomly generated png)\n  --fontsize FONTSIZE   font size of text (if desired) defaults to 16\n  --xcoord XCOORD       x coordinate for text (defaults to 0)\n  --ycoord YCOORD       y coordinate for text (defaults to 0)\n  --ca CA               the a component of the c parameter\n  --cb CB               the b component of the c parameter\n  --res RES             the resolution to generate (defaults to 1000)\n  --iter ITERS          the number of iterations per pixel (defaults to 200)\n  --color {random,pattern,glow}\n                        a color pattern to follow.\n  --rgb RGB             a specific rbg color, in format R,G,B\n  --theme {christmas,easter,fall,random,halloween,hanukkah,spring,summer,thanksgiving,valentine,winter}\n                        a theme to color the art (defaults to random colors)\n  --corpus {trump,hamlet,dr_seuss,ts_eliot,office}\n                        the corpus to use to generate text\n  --custom-corpus CUSTOM_CORPUS\n                        A custom corpus file, ending in .txt, placed in corpus\n                        folder\n  --no-model            Don't generate a sentence from corpus, just randomly\n                        select.\n  --zoom ZOOM           the level of zoom (defaults to 1.8)\n  --size SIZE           the number of words to generate\n  --alpha ALPHA         alpha (transparency) of the text (defaults to 40)\n```\n\nYou'll notice that we largely take in the same arguments as the [juliart](https://github.com/vsoch/juliart)\nmodule, and you can see the README there for details. The additional arguments added are to\nspecify a corpus, custom corpus, or choose to use a model or not.\n\n```bash\n  --corpus {trump,hamlet,dr_seuss,ts_eliot}\n                        the corpus to use to generate text\n  --custom-corpus CUSTOM_CORPUS\n                        A custom corpus file, ending in .txt, placed in corpus\n                        folder\n  --no-model            Don't generate a sentence from corpus, just randomly\n                        select.\n```\n\nAlso note that juliart \u003e= 0.0.14 is required.\n\n### Defaults\n\nBy default, we will read in a corpus in the [corpus](corpus) folder and\ngenerate a word gram (meaning an [ngram](https://en.wikipedia.org/wiki/N-gram)\nmade up of words as tokens) to generate a custom length of text. \n\n```bash\n./juliasays.py generate --outfile images/defaults.png\n```\n![images/defaults.png](images/defaults.png)\n\nThe default corpus is Dr. Seuss, one that I generated a while back.\n\n\n### Corpus\n\nYou can select any of the corpus provided in the corpus folder:\n\n```bash\n./juliasays.py generate --corpus trump --outfile images/trump.png\n./juliasays.py generate --corpus ts_eliot --outfile images/ts_eliot.png\n./juliasays.py generate --corpus hamlet --outfile images/hamlet.png\n./juliasays.py generate --corpus the_office --outfile images/office.png\n```\n\nor specify the full path to your own custom corpus file.\nFor example, I've generated lines for each of the major office characters under [corpus/office](corpus/office)\nAnd given a file `corpus/office/michael.txt` could generate a graphic like:\n\n```bash\n./juliasays.py generate --custom-corpus corpus/office/michael.txt --outfile images/michael-office.png\n```\n\n![images/micheal-office.png](images/michael-office.png)\n\nTake a look at the [corpus/office](corpus/office) folder for all the different\ncharacters. I've also provided the raw data files used to generate them.\n\n\n### Raw Text\n\nIf you don't want to use Markov generation (and just return a random set of sentences)\nyou can do that too:\n\n```bash\n./juliasays.py generate --no-model\n```\n\n### Font\n\nYou can change the font to OpenSans-Regular:\n\n```bash\n./juliasays.py generate --font OpenSans-Regular --outfile images/opensans.png\n```\n\n![images/opensans.png](images/opensans.png)\n\n\n## Markov Models\n\nI really love this model because it's so simple to work with! We basically generate\na lookup of words (tokens), where each index has a list of all the other words that\nwere found to follow it. For example:\n\n```\nblue: [one, two, three, four]\n```\n\nWould say that we parsed the text and found that the tokens \"one\" \"two\" \"three\" and \"four\"\nfollowed the word \"blue.\" In practice we can build this lookup fairly easily\nfrom a raw text corpus:\n\n```python\ndef generate_word_grams(text):\n    \"\"\"Generate a lookup of words mapped to the next occurring word, and\n       we can use this to generate new text based on occurrence.\n    \"\"\"\n    words = text.split()\n    wordgrams = {}\n\n    # Add each word to the lookup\n    for i in range(len(words) - 1):\n\n        if words[i] not in wordgrams:\n            wordgrams[words[i]] = []\n\n        # Each entry should have the next occurring word\n        wordgrams[words[i]].append(words[i + 1])\n\n    return wordgrams\n```\n\nI removed some of the subtle details, like creating an empty list potentially for\nthe last word, and making the word lowercase to streamline the lookup.\n\nOnce we have this lookup we can generate some new sentence / text of a particular\nlength simply by starting with a word, and the randomly selecting some following \nword from the list (and continuing in that fashion until we have the total\nnumber that we want).\n\n```python\ndef generate_words_markov(corpus, size=10):\n    \"\"\"Generate a word lookup based on unique words, and for each\n       have the values be the list of following words to choose from.\n       Randomly select a next word in this fashion.\n    \"\"\"\n    # Load filename into list of words\n    text = load_corpus(corpus)\n\n    # Generate words lookup\n    grams = generate_word_grams(text)\n\n    # Now generate the sentence of a particular size\n    current = random.choice(text.split())\n    result = current\n    for _ in range(size):\n\n        # Always look up entirely lowercase\n        possibilities = grams[current.lower()]\n        if len(possibilities) == 0:\n            break\n        next_word = random.choice(possibilities)\n        result = \"%s %s\" % (result, next_word)\n        current = next_word\n\n    return result\n```\n\nI also remove some detail work like capitalizing the first and ending with\na period to simplify the example. You can see the full code in [juliasays.py](juliasays.py).\n\n## Docker\n\nIf you want to build a Docker image to generate the images, you can do that:\n\n```bash\n$ docker build -t vanessa/juliart-says .\n```\n\nThen run the container and provide arguments as desired. You can bind a directory to\nsave files.\n\n```bash\n$ mkdir -p data\n$ docker run -it -v $PWD/data:/data vanessa/juliart-says generate --outfile /data/office.png --corpus the_office\n```\n\n## What did I learn?\n\n### Transparency\n\nThe coolest thing I picked up from this exercise was that to support transparency, I need\nto create a second image layer, print the transparent text to it, and then combine\nthe base image with the text as an overlay. If you don't do this (and draw the\ntext onto the same image) it doesn't generate what you'd expect for transparency,\nbecause the pixels are filled with a single transparent color without the backdrop.\n\n### Needs Love\n\nCurrently, we use the font like a background to the image moreso than a quote\nthat you can read from start to finish. It might make sense\nto have a mode that generates one or two sentences and then prints it cleanly\n(somewhere) on the image. I think I'm going to work on a separate meme generator\n(using the office and Confucius quotes) toward this goal.\n\n### Acknowledgements\n\nI didn't want to derive new corpus, so thank you to the following repositories for\nbeing able to use your corpus / share raw data:\n\n - [itsron717/markov-gen](https://github.com/itsron717/markov-gen)\n\nAll are licensed under MIT so we should be okay to share.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsoch%2Fjuliart-says","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvsoch%2Fjuliart-says","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsoch%2Fjuliart-says/lists"}