\n",
"\n",
"![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)"
]
},
{
"cell_type": "markdown",
"id": "featured-afghanistan",
"metadata": {},
"source": [
"Badania nad komputerowym wspomaganiem tłumaczenia często prowadzone są przy użyciu metodologii testowania interfejsów użytkownika - UI/UX testing. Program typu CAT traktuje się wówczas jak każdy inny program komputerowy i przeprowadza testy wydajności i użyteczności."
]
},
{
"cell_type": "markdown",
"id": "severe-protein",
"metadata": {},
"source": [
"Testy takie prowadzone są zawsze na użytkownikach końcowych, w tym przypadku - na tłumaczach. Podstawowym celem testów jest próba zaobserwowania faktycznego sposobu pracy tłumacza - które funkcje programu są przez niego wykorzystywane najczęściej, jakich innych narzędzi poza CAT-em używa on do swojej pracy, które funkcje programu działają zgodnie, a które niezgodnie z intuicją użytkownika oraz wiele innych czynników. Aby wszystkie te analizy były możliwe, konieczne jest zgromadzenie jak największej ilości danych dotyczących przebiegu testu."
]
},
{
"cell_type": "markdown",
"id": "constant-underground",
"metadata": {},
"source": [
"Testy są przede wszystkim nagrywane. Nagrywany jest zarówno ekran komputera (screen capture), jak i sam użytkownik pracujący przy komputerze. To jednak nie wszystko - często stosuje się specjalne techniki eye-trackingu, które są w stanie określić, w który punk ekranu użytkownik aktualnie patrzy. Dane pozyskane w ten sposób używane są do analizy czasu znalezienia przez użytkownika potrzebnej mu funkcji oraz zidentyfikowania miejsc, gdzie tej funkcji poszukiwał. Można również wyznaczyć obszary ekranu, które często skupiają uwagę użytkownika. "
]
},
{
"cell_type": "markdown",
"id": "analyzed-lodging",
"metadata": {},
"source": [
"Dodatkowo stosuje się jeszcze jedną technikę, która jest szczególnie przydatna z punktu widzenia analizy procesu tłumaczenia. Wykonuje się pełny key logging, tj. zapisuje się każde uderzenie użytkownika w dowolny klawisz na klawiaturze wraz z precyzyjnym czasem tego uderzenia. Dane pozyskane w ten sposób pozwalają na przeprowadzenie szeregu interesujących analiz."
]
},
{
"cell_type": "markdown",
"id": "incredible-stress",
"metadata": {},
"source": [
"Zapoznajmy się najpierw z programem typu key logger:"
]
},
{
"cell_type": "markdown",
"id": "arctic-horror",
"metadata": {},
"source": [
"`sudo pip3 install keyboard`"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "broken-workstation",
"metadata": {},
"outputs": [
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[6], line 10\u001b[0m\n\u001b[1;32m 6\u001b[0m f\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdatetime\u001b[38;5;241m.\u001b[39mnow()\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m - \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mkey\u001b[38;5;241m.\u001b[39mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 8\u001b[0m keyboard\u001b[38;5;241m.\u001b[39mon_press(log_key)\n\u001b[0;32m---> 10\u001b[0m \u001b[43mkeyboard\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwait\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/opt/homebrew/lib/python3.11/site-packages/keyboard/__init__.py:886\u001b[0m, in \u001b[0;36mwait\u001b[0;34m(hotkey, suppress, trigger_on_release)\u001b[0m\n\u001b[1;32m 884\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 885\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m--> 886\u001b[0m \u001b[43m_time\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1e6\u001b[39;49m\u001b[43m)\u001b[49m\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"import keyboard\n",
"from datetime import datetime\n",
"\n",
"def log_key(key):\n",
" with open(\"txt/keylog.txt\", \"a\") as f:\n",
" f.write(f\"{datetime.now()} - {key.name}\\n\")\n",
"\n",
"keyboard.on_press(log_key)\n",
"\n",
"keyboard.wait()\n"
]
},
{
"cell_type": "markdown",
"id": "polish-census",
"metadata": {},
"source": [
"UWAGA! Aby uruchomić powyższy kod na Linuxie konieczne są uprawnienia administratora (pytanie poza konkursem - dlaczego?)"
]
},
{
"cell_type": "markdown",
"id": "incoming-hands",
"metadata": {},
"source": [
"### Ćwiczenie 1: Wykorzystując powyższy kod napisz keylogger, który zapisuje wszystkie uderzenia w klawisze do pliku. Format pliku jest dowolny, każdy wpis musi zawierać precyzyjną godzinę uderzenia oraz uderzony klawisz. Uruchom program i przepisz paragraf dowolnie wybranego tekstu."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e1fd5d69",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
".remove_()>"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import keyboard\n",
"import time\n",
"\n",
"def log_keystroke(event):\n",
" with open(\"txt/keystroke_log.txt\", \"a\") as log_file:\n",
" log_time = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n",
" log_file.write(f\"{log_time} - {event.name}\\n\")\n",
"\n",
"keyboard.on_press(log_keystroke)\n",
"keyboard.wait()"
]
},
{
"cell_type": "markdown",
"id": "valuable-bearing",
"metadata": {},
"source": [
"Celem powyższego ćwiczenia jest pozyskanie danych testowych. Dalsze analizy będziemy prowadzili już bez key loggera, starając się korzystać jedynie z danych zapisanych w pliku. Oczywiście, jeśli zajdzie taka konieczność, można w każdej chwili wygenerować sobie nowy plik."
]
},
{
"cell_type": "markdown",
"id": "boxed-maple",
"metadata": {},
"source": [
"### Ćwiczenie 2: Napisz program, który wyliczy średnią prędkość pisania. Wykryj, kiedy użytkownik zaczął pisać. Nie bierz pod uwagę przerw dłuższych niż 5 sekund. Podaj prędkość pisania w znakach na minutę oraz słowach na minutę."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "possible-holder",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"This process is not trusted! Input event monitoring will not be possible until it is added to accessibility clients.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Rozpocznij pisanie. Naciśnij 'esc' aby zakończyć i zobaczyć wyniki.\n",
"Średnia prędkość pisania: 733.09 znaków na minutę\n",
"Średnia prędkość pisania: 146.62 słów na minutę\n"
]
}
],
"source": [
"from pynput import keyboard\n",
"from datetime import datetime, timedelta\n",
"\n",
"key_times = []\n",
"last_time = None\n",
"\n",
"def on_press(key):\n",
" global last_time\n",
" current_time = datetime.now()\n",
" \n",
" if last_time and (current_time - last_time) > timedelta(seconds=5):\n",
" key_times.clear()\n",
" \n",
" key_times.append(current_time)\n",
" last_time = current_time\n",
"\n",
"def calculate_speed():\n",
" if len(key_times) < 2:\n",
" print(\"Zbyt mało danych do obliczenia prędkości pisania.\")\n",
" return\n",
" \n",
" total_time = (key_times[-1] - key_times[0]).total_seconds() / 60\n",
" \n",
" num_chars = len(key_times)\n",
" num_words = num_chars / 5\n",
" \n",
" chars_per_minute = num_chars / total_time\n",
" words_per_minute = num_words / total_time\n",
" \n",
" print(f\"Średnia prędkość pisania: {chars_per_minute:.2f} znaków na minutę\")\n",
" print(f\"Średnia prędkość pisania: {words_per_minute:.2f} słów na minutę\")\n",
"\n",
"def on_release(key):\n",
" if key == keyboard.Key.esc:\n",
" calculate_speed()\n",
" return False\n",
"\n",
"with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:\n",
" print(\"Rozpocznij pisanie. Naciśnij 'esc' aby zakończyć i zobaczyć wyniki.\")\n",
" listener.join()\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03ba2685",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "ceramic-birth",
"metadata": {},
"source": [
"Wróćmy teraz do procesu tłumaczenia. Analiza uderzeń klawiszy wykonanych podczas tłumaczenia pozwala wykryć dłuższe pauzy. Pauzy te najczęściej wskazują miejsca, w których tłumacz musi się głębiej zastanowić nad tłumaczeniem danego słowa lub frazy. Przerwę tę wykorzystuje na przykład na sprawdzenie tłumaczenia lub definicji w słowniku, przeglądanie wyników z pamięci tłumaczeń lub korzystanie z innych pomocy (eye-tracking mógłby w tym przypadku rozstrzygnąć, czym w istocie zajmuje się w tym momencie tłuamcz). Jest też możliwe, że tłumacz poświęca pauzę na tzw. cognitive pause-and-unload - rodzaj zamyślenia, pozwalający oczyścić myśli. Z punktu widzenia projektowania systemu wspomagającego tłumaczenie niezwykle istotna jest informacja, nad czym tłumacz musi się dłużej zastanowić. Minimalizacja liczby i czasu trwania takich przerw jest szansą na usprawnienie procesu tłumaczenia."
]
},
{
"cell_type": "markdown",
"id": "great-cable",
"metadata": {},
"source": [
"### Ćwiczenie 3: Napisz program do wykrywania przerw w pisaniu. Raportuj długość oraz miejsce wystąpienia przerwy podając 20-znakowy kontekst z każdej strony. Wykryj każdą przerwę dłuższą niż 3 sekundy, posortuj wyniki malejąco po długości przerwy."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "close-riverside",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"This process is not trusted! Input event monitoring will not be possible until it is added to accessibility clients.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Rozpocznij pisanie. Naciśnij 'esc' aby zakończyć i zobaczyć wyniki.\n",
"Przerwa: 3.82 sekund\n",
"Kontekst przed przerwą: ...fsdjfdnsjfjdnfjdfnj[Key.esc]\n",
"Kontekst po przerwie: ...\n",
"\n"
]
}
],
"source": [
"from pynput import keyboard\n",
"from datetime import datetime, timedelta\n",
"\n",
"key_times = []\n",
"key_text = []\n",
"last_time = None\n",
"breaks = []\n",
"\n",
"def key_to_string(key):\n",
" try:\n",
" return key.char\n",
" except AttributeError:\n",
" if key == keyboard.Key.space:\n",
" return ' '\n",
" return f'[{str(key)}]'\n",
"\n",
"def on_press(key):\n",
" global last_time\n",
" current_time = datetime.now()\n",
" \n",
" key_times.append(current_time)\n",
" key_text.append(key_to_string(key))\n",
" \n",
" if last_time and (current_time - last_time) > timedelta(seconds=3):\n",
" context_start = max(0, len(key_text) - 20)\n",
" context_end = len(key_text)\n",
" context_before = ''.join(key_text[context_start:context_end])\n",
" \n",
" context_start = len(key_text)\n",
" context_end = min(len(key_text) + 20, len(key_text))\n",
" context_after = ''.join(key_text[context_start:context_end])\n",
" \n",
" breaks.append((current_time - last_time, context_before, context_after))\n",
" \n",
" last_time = current_time\n",
"\n",
"def report_breaks():\n",
" if not breaks:\n",
" print(\"Nie wykryto przerw dłuższych niż 3 sekundy.\")\n",
" return\n",
" \n",
" breaks.sort(reverse=True, key=lambda x: x[0])\n",
" \n",
" for duration, context_before, context_after in breaks:\n",
" print(f\"Przerwa: {duration.total_seconds():.2f} sekund\")\n",
" print(f\"Kontekst przed przerwą: ...{context_before}\")\n",
" print(f\"Kontekst po przerwie: {context_after}...\")\n",
" print()\n",
"\n",
"def on_release(key):\n",
" if key == keyboard.Key.esc:\n",
" report_breaks()\n",
" return False\n",
"\n",
"with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:\n",
" print(\"Rozpocznij pisanie. Naciśnij 'esc' aby zakończyć i zobaczyć wyniki.\")\n",
" listener.join()\n",
"\n"
]
}
],
"metadata": {
"author": "Rafał Jaworski",
"email": "rjawor@amu.edu.pl",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"lang": "pl",
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
},
"subtitle": "12. Key logging",
"title": "Komputerowe wspomaganie tłumaczenia",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 5
}