Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring OCR to Mealie for importing scanned recipes #1244

Closed
wants to merge 61 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c712e2c
Add pytesseract
Miroito May 17, 2022
cd164b4
Add simple ocr endpoint
Miroito May 17, 2022
42b0fbe
feat/ocr-editor gui
Miroito Jul 14, 2022
13c5fe5
fix frontend linting issues
Miroito Jul 14, 2022
362cafd
Add service unit tests
Miroito Jul 16, 2022
65fb53e
Add split text modes & single ingredient/instruction editing
Miroito Jul 16, 2022
050b0f2
make split mode really reactive
Miroito Jul 16, 2022
18aa20d
Remove default step and ingredient
Miroito Jul 17, 2022
9ff6427
make the linter haappy
Miroito Jul 17, 2022
66d558a
Accept only image uploads
Miroito Jul 17, 2022
c0cb82f
Add automatic recipe title suggestion
Miroito Jul 18, 2022
a283c43
Correct regex
Miroito Jul 18, 2022
52348c9
fix incorrect array.map method usage
Miroito Jul 18, 2022
2f32824
make the linter happy again
Miroito Jul 18, 2022
d4f03f6
Swap route to use asset name
Miroito Jul 22, 2022
16c28b8
Rearange buttons
Miroito Jul 22, 2022
651b30e
fix test data
Miroito Jul 25, 2022
bf9e9a9
feat: Allow making image the recipe image
Miroito Jul 25, 2022
1c56790
Add translation
Miroito Jul 25, 2022
b1c45db
Make the linter happy
Miroito Jul 25, 2022
e6b80e1
Restrict function setPropertyValueByPath generic
Miroito Jul 26, 2022
1398164
Restrict template literal type
Miroito Jul 28, 2022
91d441f
Add a more friendly icon to creation page
Miroito Jul 28, 2022
4b8bb70
update poetry lock file
hay-kot Jul 28, 2022
15a4046
Correct sloppy ocr classes
Miroito Jul 29, 2022
f46dc09
Make MyPy happy
Miroito Jul 30, 2022
9ca7047
Rewrite safer tests
Miroito Jul 30, 2022
20b3f43
Add tesseract to backend test CI container dependencies
Miroito Jul 30, 2022
6c50c31
Make canvas element a component global
Miroito Aug 1, 2022
5066375
Remove unwanted spaces in selected text
Miroito Aug 1, 2022
c41f9fe
Add way to know if recipe was created with ocr
Miroito Aug 5, 2022
5758f24
Access to ocr-editor for ocr recipes
Miroito Aug 5, 2022
84deba7
Update Alembic revision
Miroito Aug 5, 2022
9ecd20c
Make the frontend build
Miroito Aug 6, 2022
ca58079
Fix scrolling offset bug
Miroito Aug 7, 2022
9e70552
Allow creation of recipes with custom settings
Miroito Aug 8, 2022
95e330f
Fix rebasing mistakes
Miroito Aug 8, 2022
133d663
Add format_tsv_output test
Miroito Aug 8, 2022
6359962
Exclude the tests data directory only
Miroito Aug 9, 2022
78eb080
Enforce camelCase for frontend functions
Miroito Aug 9, 2022
2dd2989
Remove import of unused component
Miroito Aug 9, 2022
c260dbf
Fix type and class initialization
Miroito Aug 13, 2022
b38ae66
Add multi-language support
Miroito Aug 13, 2022
17cad03
Highlight words in mount
Miroito Aug 19, 2022
3731746
Fix image ratio bug
Miroito Aug 20, 2022
fc7221c
Better ocr creation page
Miroito Sep 3, 2022
35f4049
Revert awkward feature to scroll in Selection mode
Miroito Sep 3, 2022
965c85c
Rebasing alembic migrations sux
Miroito Sep 3, 2022
e7f15ef
Remove obsolete getShared function
Miroito Sep 3, 2022
7941a76
Add function docstring
Miroito Sep 11, 2022
0a8412c
Move down ocr creation option
Miroito Sep 11, 2022
0215ec5
Make toolbar icons more generic
Miroito Sep 12, 2022
ec27d35
Show help at the bottom of the page
Miroito Sep 16, 2022
53f13ed
move ocr types to own file
Miroito Sep 22, 2022
7907548
Use template ref for the canvas
Miroito Sep 22, 2022
a5d08b2
Use i18n.tc to get strings directly
Miroito Sep 22, 2022
734ade1
Correct naming mistake
Miroito Sep 22, 2022
27d19cb
Move Ocr editor to own directory
Miroito Sep 24, 2022
3ff376d
Create Ocr Editor parts
Miroito Sep 24, 2022
ac9da82
Safeguard recipe properties access
Miroito Sep 24, 2022
ad59df2
Add loading frontend animation due to longer request time
Miroito Sep 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/partial-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev tesseract-ocr-all
poetry install
poetry add "psycopg2-binary==2.8.6"
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repos:
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
exclude: ^tests/data/
- repo: https://github.com/sondrelg/pep585-upgrade
rev: "v1.0.1" # Use the sha / tag you want to point at
hooks:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Add is_ocr_recipe column to recipes

Revision ID: 089bfa50d0ed
Revises: f30cf048c228
Create Date: 2022-08-05 17:07:07.389271

"""
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = "089bfa50d0ed"
down_revision = "188374910655"
branch_labels = None
depends_on = None


def upgrade():
op.add_column("recipes", sa.Column("is_ocr_recipe", sa.Boolean(), default=False, nullable=True))
op.execute("UPDATE recipes SET is_ocr_recipe = FALSE")
# SQLITE does not support ALTER COLUMN, so the column will stay nullable to prevent making this migration a mess
# The Recipe pydantic model and the SQL server use False as default value anyway for this column so Null should be a very rare sight


def downgrade():
op.drop_column("recipes", "is_ocr_recipe")
18 changes: 18 additions & 0 deletions frontend/api/class-interfaces/ocr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { BaseAPI } from "~/api/_base";

const prefix = "/api";

export class OcrAPI extends BaseAPI {

// Currently unused in favor for the endpoint using asset names
async fileToTsv(file: File) {
const formData = new FormData();
formData.append("file", file);
return await this.requests.post(`${prefix}/ocr/file-to-tsv`, formData);
}

async assetToTsv(recipeSlug: string, assetName: string) {
return await this.requests.post(`${prefix}/ocr/asset-to-tsv`, { recipeSlug, assetName });
}

}
10 changes: 10 additions & 0 deletions frontend/api/class-interfaces/recipes/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const routes = {
recipesCategory: `${prefix}/recipes/category`,
recipesParseIngredient: `${prefix}/parser/ingredient`,
recipesParseIngredients: `${prefix}/parser/ingredients`,
recipesCreateFromOcr: `${prefix}/recipes/create-ocr`,

recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`,
recipesRecipeSlugExport: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/exports`,
Expand Down Expand Up @@ -116,4 +117,13 @@ export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
getZipRedirectUrl(recipeSlug: string, token: string) {
return `${routes.recipesRecipeSlugExportZip(recipeSlug)}?token=${token}`;
}

async createFromOcr(file: File, makeFileRecipeImage: boolean) {
const formData = new FormData();
formData.append("file", file);
formData.append("extension", file.name.split(".").pop() ?? "");
formData.append("makefilerecipeimage", String(makeFileRecipeImage));

return await this.requests.post(routes.recipesCreateFromOcr, formData);
}
}
5 changes: 5 additions & 0 deletions frontend/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose
import { GroupEventNotifierApi } from "./class-interfaces/group-event-notifier";
import { MealPlanRulesApi } from "./class-interfaces/group-mealplan-rules";
import { GroupDataSeederApi } from "./class-interfaces/group-seeder";
import {OcrAPI} from "./class-interfaces/ocr";
import { ApiRequestInstance } from "~/types/api";

class Api {
Expand Down Expand Up @@ -52,6 +53,7 @@ class Api {
public groupEventNotifier: GroupEventNotifierApi;
public upload: UploadFile;
public seeders: GroupDataSeederApi;
public ocr: OcrAPI;

constructor(requests: ApiRequestInstance) {
// Recipes
Expand Down Expand Up @@ -90,6 +92,9 @@ class Api {
this.bulk = new BulkActionsAPI(requests);
this.groupEventNotifier = new GroupEventNotifierApi(requests);

// ocr
this.ocr = new OcrAPI(requests);

Object.freeze(this);
}
}
Expand Down
23 changes: 16 additions & 7 deletions frontend/components/Domain/Recipe/RecipeActionMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const SAVE_EVENT = "save";
const DELETE_EVENT = "delete";
const CLOSE_EVENT = "close";
const JSON_EVENT = "json";
const OCR_EVENT = "ocr";

export default defineComponent({
components: { RecipeContextMenu, RecipeFavoriteBadge },
Expand Down Expand Up @@ -122,8 +123,12 @@ export default defineComponent({
type: Boolean,
default: false,
},
showOcrButton: {
type: Boolean,
default: false,
},
},
setup(_, context) {
setup(props, context) {
const deleteDialog = ref(false);

const { i18n, $globals } = useContext();
Expand Down Expand Up @@ -154,22 +159,26 @@ export default defineComponent({
},
];

if (props.showOcrButton) {
editorButtons.splice(2, 0, {
text: i18n.t("ocr-editor.ocr-editor"),
icon: $globals.icons.eye,
event: OCR_EVENT,
color: "accent",
});
}

function emitHandler(event: string) {
switch (event) {
case CLOSE_EVENT:
context.emit(CLOSE_EVENT);
context.emit("input", false);
break;
case SAVE_EVENT:
context.emit(SAVE_EVENT);
break;
case JSON_EVENT:
context.emit(JSON_EVENT);
break;
case DELETE_EVENT:
deleteDialog.value = true;
break;
default:
context.emit(event);
break;
}
}
Expand Down
13 changes: 10 additions & 3 deletions frontend/components/Domain/Recipe/RecipeDialogBulkAdd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="text-center">
<v-dialog v-model="dialog" width="800">
<template #activator="{ on, attrs }">
<BaseButton v-bind="attrs" v-on="on" @click="inputText = ''">
<BaseButton v-bind="attrs" v-on="on" @click="inputText = inputTextProp">
{{ $t("new-recipe.bulk-add") }}
</BaseButton>
</template>
Expand Down Expand Up @@ -58,10 +58,17 @@
<script lang="ts">
import { reactive, toRefs, defineComponent, useContext } from "@nuxtjs/composition-api";
export default defineComponent({
setup(_, context) {
props: {
inputTextProp: {
type: String,
required: false,
default: "",
},
},
setup(props, context) {
const state = reactive({
dialog: false,
inputText: "",
inputText: props.inputTextProp,
});

function splitText() {
Expand Down
11 changes: 10 additions & 1 deletion frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class="mx-1 mt-3 mb-4"
:placeholder="$t('recipe.section-title')"
style="max-width: 500px"
@click="$emit('clickIngredientField', 'title')"
>
</v-text-field>
<v-row :no-gutters="$vuetify.breakpoint.mdAndUp" dense class="d-flex flex-wrap my-1">
Expand Down Expand Up @@ -81,7 +82,15 @@
</v-col>
<v-col sm="12" md="" cols="12">
<div class="d-flex">
<v-text-field v-model="value.note" hide-details dense solo class="mx-1" :placeholder="$t('recipe.notes')">
<v-text-field
v-model="value.note"
hide-details
dense
solo
class="mx-1"
placeholder="$t('recipe.notes')"
@click="$emit('clickIngredientField', 'note')"
>
<v-icon v-if="disableAmount && $listeners && $listeners.delete" slot="prepend" class="mr-n1 handle">
{{ $globals.icons.arrowUpDown }}
</v-icon>
Expand Down
1 change: 1 addition & 0 deletions frontend/components/Domain/Recipe/RecipeInstructions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
blur: imageUploadMode,
}"
@drop.stop.prevent="handleImageDrop(index, $event)"
@click="$emit('clickInstructionField', `${index}.text`)"
>
<MarkdownEditor
v-model="value[index]['text']"
Expand Down
Loading