cv-study-utils 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: cv-study-utils
3
+ Version: 0.1.0
4
+ Summary: Notebook-friendly study utilities for computer vision theory and practice snippets.
5
+ Author: CV Exam Helper
6
+ License-Expression: MIT
7
+ Keywords: computer-vision,exam,jupyter,colab
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Intended Audience :: Education
11
+ Classifier: Topic :: Education
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Provides-Extra: clipboard
15
+ Requires-Dist: pyperclip>=1.8.2; extra == "clipboard"
16
+
17
+ # CV Study Utils
18
+
19
+ Небольшая библиотека для подготовки к экзамену по машинному зрению. Она выводит ответы на теоретические вопросы и готовые кодовые шаблоны для практических заданий прямо в Jupyter/Colab.
20
+
21
+ ## Установка
22
+
23
+ Из этой папки:
24
+
25
+ ```bash
26
+ pip install -e .
27
+ ```
28
+
29
+ После загрузки проекта на GitHub в Colab/Jupyter можно будет ставить так:
30
+
31
+ ```python
32
+ !pip install git+https://github.com/<your-user>/<your-repo>.git
33
+ ```
34
+
35
+ После публикации на PyPI:
36
+
37
+ ```python
38
+ !pip install cv-study-utils
39
+ ```
40
+
41
+ ## Использование
42
+
43
+ ```python
44
+ from cv_study_utils import theory, practice, find
45
+
46
+ theory(5) # ответ на теоретический вопрос 5
47
+ practice(3) # код для практического задания 3
48
+ find("Canny") # поиск по теории и практике
49
+ practice("границ") # найти практику по фрагменту текста
50
+
51
+ theory(12, copy=True) # скопировать ответ, если доступен clipboard
52
+ practice(15, copy=True) # в ноутбуке появится кнопка Copy, если pyperclip недоступен
53
+ ```
54
+
55
+ Командная строка:
56
+
57
+ ```bash
58
+ cvstudy theory 5
59
+ cvstudy practice 3 --copy
60
+ cvstudy find "Vision Transformer"
61
+ ```
62
+
63
+ ## Что внутри
64
+
65
+ - `cv_study_utils/theory_answers.py` - 48 кратких ответов на теоретические вопросы.
66
+ - `cv_study_utils/theory_extras.py` - формулы, схемы архитектур и внешние источники для теории.
67
+ - `cv_study_utils/practice_solutions.py` - 15 готовых кодовых шаблонов для практических заданий.
68
+ - `content/theory_answers.md` и `content/practice_solutions.py` - человекочитаемые экспортные файлы, которые можно открыть отдельно.
69
+ - `Ответы_теория_МЗ2026.docx` и `Ответы_практика_МЗ2026.docx` - Word-версии материалов.
70
+ - `dist/` - готовые wheel/sdist артефакты для загрузки на PyPI.
71
+
72
+ ## Источники
73
+
74
+ Основой являются файлы из папки экзамена и лекции `lecture_2` - `Lecture_8`. Для актуализации API и формулировок использованы официальные документации OpenCV, Pillow, Matplotlib, scikit-image, scikit-learn, PyTorch/torchvision, TensorFlow/Keras, Ultralytics YOLO, а также оригинальные статьи R-CNN/Fast R-CNN/Faster R-CNN, YOLO, SSD, U-Net, Mask R-CNN, GAN, DDPM, Transformer, ViT и image captioning.
@@ -0,0 +1,58 @@
1
+ # CV Study Utils
2
+
3
+ Небольшая библиотека для подготовки к экзамену по машинному зрению. Она выводит ответы на теоретические вопросы и готовые кодовые шаблоны для практических заданий прямо в Jupyter/Colab.
4
+
5
+ ## Установка
6
+
7
+ Из этой папки:
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ После загрузки проекта на GitHub в Colab/Jupyter можно будет ставить так:
14
+
15
+ ```python
16
+ !pip install git+https://github.com/<your-user>/<your-repo>.git
17
+ ```
18
+
19
+ После публикации на PyPI:
20
+
21
+ ```python
22
+ !pip install cv-study-utils
23
+ ```
24
+
25
+ ## Использование
26
+
27
+ ```python
28
+ from cv_study_utils import theory, practice, find
29
+
30
+ theory(5) # ответ на теоретический вопрос 5
31
+ practice(3) # код для практического задания 3
32
+ find("Canny") # поиск по теории и практике
33
+ practice("границ") # найти практику по фрагменту текста
34
+
35
+ theory(12, copy=True) # скопировать ответ, если доступен clipboard
36
+ practice(15, copy=True) # в ноутбуке появится кнопка Copy, если pyperclip недоступен
37
+ ```
38
+
39
+ Командная строка:
40
+
41
+ ```bash
42
+ cvstudy theory 5
43
+ cvstudy practice 3 --copy
44
+ cvstudy find "Vision Transformer"
45
+ ```
46
+
47
+ ## Что внутри
48
+
49
+ - `cv_study_utils/theory_answers.py` - 48 кратких ответов на теоретические вопросы.
50
+ - `cv_study_utils/theory_extras.py` - формулы, схемы архитектур и внешние источники для теории.
51
+ - `cv_study_utils/practice_solutions.py` - 15 готовых кодовых шаблонов для практических заданий.
52
+ - `content/theory_answers.md` и `content/practice_solutions.py` - человекочитаемые экспортные файлы, которые можно открыть отдельно.
53
+ - `Ответы_теория_МЗ2026.docx` и `Ответы_практика_МЗ2026.docx` - Word-версии материалов.
54
+ - `dist/` - готовые wheel/sdist артефакты для загрузки на PyPI.
55
+
56
+ ## Источники
57
+
58
+ Основой являются файлы из папки экзамена и лекции `lecture_2` - `Lecture_8`. Для актуализации API и формулировок использованы официальные документации OpenCV, Pillow, Matplotlib, scikit-image, scikit-learn, PyTorch/torchvision, TensorFlow/Keras, Ultralytics YOLO, а также оригинальные статьи R-CNN/Fast R-CNN/Faster R-CNN, YOLO, SSD, U-Net, Mask R-CNN, GAN, DDPM, Transformer, ViT и image captioning.
@@ -0,0 +1,25 @@
1
+ """Convenient helpers for the computer vision exam materials."""
2
+
3
+ from .api import (
4
+ all_practice,
5
+ all_theory,
6
+ copy_practice,
7
+ copy_theory,
8
+ find,
9
+ list_practice,
10
+ list_theory,
11
+ practice,
12
+ theory,
13
+ )
14
+
15
+ __all__ = [
16
+ "all_practice",
17
+ "all_theory",
18
+ "copy_practice",
19
+ "copy_theory",
20
+ "find",
21
+ "list_practice",
22
+ "list_theory",
23
+ "practice",
24
+ "theory",
25
+ ]
@@ -0,0 +1,271 @@
1
+ from __future__ import annotations
2
+
3
+ import difflib
4
+ import html
5
+ import json
6
+ import re
7
+ import textwrap
8
+ from typing import Any, Iterable
9
+
10
+ from .practice_solutions import PRACTICE
11
+ from .theory_answers import THEORY
12
+ from .theory_extras import THEORY_EXTRAS
13
+
14
+
15
+ def _normalize(text: str) -> str:
16
+ return re.sub(r"\s+", " ", text.casefold()).strip()
17
+
18
+
19
+ def _score(query: str, item_text: str) -> float:
20
+ q = _normalize(query)
21
+ hay = _normalize(item_text)
22
+ if not q:
23
+ return 0.0
24
+ if q in hay:
25
+ return 1.0
26
+ words = [w for w in re.split(r"\W+", q) if len(w) > 2]
27
+ if words:
28
+ overlap = sum(1 for w in words if w in hay) / len(words)
29
+ else:
30
+ overlap = 0.0
31
+ ratio = difflib.SequenceMatcher(None, q, hay[: max(200, len(q) * 4)]).ratio()
32
+ return max(overlap, ratio)
33
+
34
+
35
+ def _item_text(item: dict[str, Any], kind: str) -> str:
36
+ if kind == "theory":
37
+ extra = THEORY_EXTRAS.get(item["id"], {})
38
+ formulae = " ".join(extra.get("formulae", []))
39
+ structure = " ".join(extra.get("structure", []))
40
+ return f"{item['question']} {item['answer']} {formulae} {structure}"
41
+ return f"{item['task']} {item['description']} {item['code']}"
42
+
43
+
44
+ def _title_text(item: dict[str, Any]) -> str:
45
+ return item.get("question") or item.get("task") or ""
46
+
47
+
48
+ def _rank_score(query: str, item: dict[str, Any], kind: str) -> float:
49
+ base = _score(query, _item_text(item, kind))
50
+ q = _normalize(query)
51
+ title = _normalize(_title_text(item))
52
+ if q and q in title:
53
+ base += 0.75
54
+ return base
55
+
56
+
57
+ def _get_by_number(items: list[dict[str, Any]], number: int) -> dict[str, Any]:
58
+ for item in items:
59
+ if item["id"] == number:
60
+ return item
61
+ raise ValueError(f"Нет элемента с номером {number}.")
62
+
63
+
64
+ def _best_match(items: list[dict[str, Any]], query: str, kind: str) -> dict[str, Any]:
65
+ ranked = sorted(
66
+ ((_rank_score(query, item, kind), item) for item in items),
67
+ key=lambda pair: pair[0],
68
+ reverse=True,
69
+ )
70
+ if not ranked or ranked[0][0] < 0.18:
71
+ raise ValueError(f"Ничего не найдено по запросу: {query!r}.")
72
+ return ranked[0][1]
73
+
74
+
75
+ def _display_markdown(markdown: str) -> None:
76
+ try:
77
+ from IPython.display import Markdown, display
78
+
79
+ display(Markdown(markdown))
80
+ except Exception:
81
+ print(markdown)
82
+
83
+
84
+ def _display_code(code: str) -> None:
85
+ try:
86
+ from IPython.display import Markdown, display
87
+
88
+ display(Markdown("```python\n" + code.strip() + "\n```"))
89
+ except Exception:
90
+ print(code)
91
+
92
+
93
+ def copy_text(text: str) -> str:
94
+ """Copy text if possible; in notebooks, fall back to a Copy button."""
95
+ try:
96
+ import pyperclip
97
+
98
+ pyperclip.copy(text)
99
+ return "clipboard"
100
+ except Exception:
101
+ pass
102
+
103
+ try:
104
+ from IPython.display import HTML, display
105
+
106
+ payload = json.dumps(text)
107
+ display(
108
+ HTML(
109
+ """
110
+ <button style="padding:6px 10px;border:1px solid #888;border-radius:6px"
111
+ onclick='navigator.clipboard.writeText(%s); this.textContent="Copied";'>
112
+ Copy answer
113
+ </button>
114
+ """
115
+ % payload
116
+ )
117
+ )
118
+ return "button"
119
+ except Exception as exc:
120
+ raise RuntimeError(
121
+ "Не удалось скопировать текст. Установите pyperclip или используйте Jupyter/Colab."
122
+ ) from exc
123
+
124
+
125
+ def _format_theory_item(item: dict[str, Any]) -> str:
126
+ extra = THEORY_EXTRAS.get(item["id"], {})
127
+ parts = [f"### {item['id']}. {item['question']}", "", item["answer"]]
128
+ if extra.get("formulae"):
129
+ parts.extend(["", "**Формулы:**"])
130
+ parts.extend(f"- {line}" for line in extra["formulae"])
131
+ if extra.get("structure"):
132
+ parts.extend(["", "**Структура / схема:**"])
133
+ parts.extend(f"- {line}" for line in extra["structure"])
134
+ sources = ", ".join([*item.get("sources", []), *extra.get("sources", [])])
135
+ source_line = f"\n\nИсточники: {sources}" if sources else ""
136
+ return "\n".join(parts) + source_line
137
+
138
+
139
+ def _format_practice_item(item: dict[str, Any], include_task: bool = True) -> str:
140
+ header = ""
141
+ if include_task:
142
+ header = (
143
+ f"# Практика {item['id']}. {item['task']}\n"
144
+ f"# {item['description']}\n\n"
145
+ )
146
+ return header + item["code"].strip() + "\n"
147
+
148
+
149
+ def list_theory(display: bool = True) -> str:
150
+ text = "\n".join(f"{item['id']}. {item['question']}" for item in THEORY)
151
+ if display:
152
+ _display_markdown(text)
153
+ return text
154
+
155
+
156
+ def list_practice(display: bool = True) -> str:
157
+ text = "\n".join(f"{item['id']}. {item['task']}" for item in PRACTICE)
158
+ if display:
159
+ _display_markdown(text)
160
+ return text
161
+
162
+
163
+ def theory(
164
+ number: int | str | None = None,
165
+ *,
166
+ query: str | None = None,
167
+ copy: bool = False,
168
+ display: bool = True,
169
+ ) -> str:
170
+ """Show a theory answer by number or approximate query."""
171
+ if isinstance(number, str) and query is None:
172
+ query = number
173
+ number = None
174
+ if number is None and query is None:
175
+ return list_theory(display=display)
176
+ item = _best_match(THEORY, query, "theory") if query else _get_by_number(THEORY, int(number))
177
+ text = _format_theory_item(item)
178
+ if copy:
179
+ copy_text(text)
180
+ if display:
181
+ _display_markdown(text)
182
+ return text
183
+
184
+
185
+ def practice(
186
+ number: int | str | None = None,
187
+ *,
188
+ query: str | None = None,
189
+ copy: bool = False,
190
+ display: bool = True,
191
+ include_task: bool = True,
192
+ ) -> str:
193
+ """Show a practical code template by number or approximate query."""
194
+ if isinstance(number, str) and query is None:
195
+ query = number
196
+ number = None
197
+ if number is None and query is None:
198
+ return list_practice(display=display)
199
+ item = _best_match(PRACTICE, query, "practice") if query else _get_by_number(PRACTICE, int(number))
200
+ code = _format_practice_item(item, include_task=include_task)
201
+ if copy:
202
+ copy_text(code)
203
+ if display:
204
+ _display_code(code)
205
+ return code
206
+
207
+
208
+ def find(query: str, *, kind: str = "all", limit: int = 8, display: bool = True) -> list[dict[str, Any]]:
209
+ """Search theory and/or practice by a text fragment."""
210
+ pools: list[tuple[str, list[dict[str, Any]]]] = []
211
+ if kind in {"all", "theory"}:
212
+ pools.append(("theory", THEORY))
213
+ if kind in {"all", "practice"}:
214
+ pools.append(("practice", PRACTICE))
215
+ if not pools:
216
+ raise ValueError("kind должен быть 'all', 'theory' или 'practice'.")
217
+
218
+ found: list[dict[str, Any]] = []
219
+ for pool_kind, items in pools:
220
+ for item in items:
221
+ score = _rank_score(query, item, pool_kind)
222
+ if score >= 0.18:
223
+ found.append(
224
+ {
225
+ "kind": pool_kind,
226
+ "id": item["id"],
227
+ "title": _title_text(item),
228
+ "score": round(score, 3),
229
+ }
230
+ )
231
+ found.sort(key=lambda row: row["score"], reverse=True)
232
+ found = found[:limit]
233
+ if display:
234
+ if found:
235
+ lines = [
236
+ f"- `{row['kind']}({row['id']})` - {html.escape(row['title'])} "
237
+ f"(score {row['score']})"
238
+ for row in found
239
+ ]
240
+ _display_markdown("\n".join(lines))
241
+ else:
242
+ _display_markdown("Ничего не найдено.")
243
+ return found
244
+
245
+
246
+ def all_theory(*, copy: bool = False, display: bool = True) -> str:
247
+ text = "\n\n".join(_format_theory_item(item) for item in THEORY)
248
+ if copy:
249
+ copy_text(text)
250
+ if display:
251
+ _display_markdown(text)
252
+ return text
253
+
254
+
255
+ def all_practice(*, copy: bool = False, display: bool = True) -> str:
256
+ text = "\n\n".join(
257
+ "```python\n" + _format_practice_item(item).strip() + "\n```" for item in PRACTICE
258
+ )
259
+ if copy:
260
+ copy_text(text)
261
+ if display:
262
+ _display_markdown(text)
263
+ return text
264
+
265
+
266
+ def copy_theory(number: int) -> str:
267
+ return theory(number, copy=True, display=False)
268
+
269
+
270
+ def copy_practice(number: int) -> str:
271
+ return practice(number, copy=True, display=False)
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from .api import all_practice, all_theory, find, list_practice, list_theory, practice, theory
6
+
7
+
8
+ def main() -> None:
9
+ parser = argparse.ArgumentParser(prog="cvstudy")
10
+ sub = parser.add_subparsers(dest="command", required=True)
11
+
12
+ p_theory = sub.add_parser("theory", help="Show a theory answer")
13
+ p_theory.add_argument("number", nargs="?")
14
+ p_theory.add_argument("--query", "-q")
15
+ p_theory.add_argument("--copy", action="store_true")
16
+ p_theory.add_argument("--all", action="store_true")
17
+
18
+ p_practice = sub.add_parser("practice", help="Show a practical code template")
19
+ p_practice.add_argument("number", nargs="?")
20
+ p_practice.add_argument("--query", "-q")
21
+ p_practice.add_argument("--copy", action="store_true")
22
+ p_practice.add_argument("--all", action="store_true")
23
+
24
+ p_find = sub.add_parser("find", help="Search theory and practice")
25
+ p_find.add_argument("query")
26
+ p_find.add_argument("--kind", choices=["all", "theory", "practice"], default="all")
27
+ p_find.add_argument("--limit", type=int, default=8)
28
+
29
+ args = parser.parse_args()
30
+ if args.command == "theory":
31
+ if args.all:
32
+ print(all_theory(copy=args.copy, display=False))
33
+ elif args.number is None and args.query is None:
34
+ print(list_theory(display=False))
35
+ else:
36
+ number = int(args.number) if args.number and args.number.isdigit() else args.number
37
+ print(theory(number, query=args.query, copy=args.copy, display=False))
38
+ elif args.command == "practice":
39
+ if args.all:
40
+ print(all_practice(copy=args.copy, display=False))
41
+ elif args.number is None and args.query is None:
42
+ print(list_practice(display=False))
43
+ else:
44
+ number = int(args.number) if args.number and args.number.isdigit() else args.number
45
+ print(practice(number, query=args.query, copy=args.copy, display=False))
46
+ elif args.command == "find":
47
+ rows = find(args.query, kind=args.kind, limit=args.limit, display=False)
48
+ for row in rows:
49
+ print(f"{row['kind']}({row['id']}): {row['title']} [score={row['score']}]")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()