patkit 0.22.1__tar.gz → 0.22.2__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.
Files changed (119) hide show
  1. {patkit-0.22.1 → patkit-0.22.2}/PKG-INFO +5 -6
  2. {patkit-0.22.1 → patkit-0.22.2}/README.md +4 -5
  3. {patkit-0.22.1 → patkit-0.22.2}/patkit/configuration/configuration.py +4 -3
  4. {patkit-0.22.1 → patkit-0.22.2}/patkit/constants.py +64 -1
  5. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_structures/__init__.py +1 -1
  6. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_structures/base_classes.py +1 -1
  7. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_structures/data_structures.py +110 -33
  8. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_structures/metadata_classes.py +49 -1
  9. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/__init__.py +3 -0
  10. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/annotator_window.py +103 -35
  11. patkit-0.22.2/patkit/gui/audio_player.py +141 -0
  12. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/boundary_animation.py +4 -4
  13. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/dialog_template.py +3 -3
  14. patkit-0.22.2/patkit/gui/exercise_dialogs.py +190 -0
  15. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/list_selection_dialog.py +31 -0
  16. patkit-0.22.2/patkit/gui/plot_controller.py +905 -0
  17. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/select_scenario_dialog.py +5 -3
  18. {patkit-0.22.1 → patkit-0.22.2}/patkit/patgrid.py +18 -4
  19. {patkit-0.22.1 → patkit-0.22.2}/patkit/path_resolution.py +31 -0
  20. {patkit-0.22.1 → patkit-0.22.2}/patkit/plot_and_publish/__init__.py +2 -1
  21. {patkit-0.22.1 → patkit-0.22.2}/patkit/qt_annotator.py +329 -807
  22. {patkit-0.22.1 → patkit-0.22.2}/patkit/save_and_load/__init__.py +7 -2
  23. {patkit-0.22.1 → patkit-0.22.2}/patkit/save_and_load/load.py +113 -7
  24. {patkit-0.22.1 → patkit-0.22.2}/patkit/save_and_load/save.py +77 -6
  25. {patkit-0.22.1 → patkit-0.22.2}/patkit/save_and_load/save_and_load_schemas.py +31 -1
  26. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/simulate.py +3 -2
  27. {patkit-0.22.1 → patkit-0.22.2}/pyproject.toml +10 -1
  28. {patkit-0.22.1 → patkit-0.22.2}/.gitignore +0 -0
  29. {patkit-0.22.1 → patkit-0.22.2}/LICENSE.markdown +0 -0
  30. {patkit-0.22.1 → patkit-0.22.2}/patkit/__init__.py +0 -0
  31. {patkit-0.22.1 → patkit-0.22.2}/patkit/annotations/__init__.py +0 -0
  32. {patkit-0.22.1 → patkit-0.22.2}/patkit/annotations/peaks.py +0 -0
  33. {patkit-0.22.1 → patkit-0.22.2}/patkit/audio_processing/__init__.py +0 -0
  34. {patkit-0.22.1 → patkit-0.22.2}/patkit/audio_processing/audio_processing.py +0 -0
  35. {patkit-0.22.1 → patkit-0.22.2}/patkit/cli.py +0 -0
  36. {patkit-0.22.1 → patkit-0.22.2}/patkit/cli_commands.py +0 -0
  37. {patkit-0.22.1 → patkit-0.22.2}/patkit/computational.py +0 -0
  38. {patkit-0.22.1 → patkit-0.22.2}/patkit/configuration/__init__.py +0 -0
  39. {patkit-0.22.1 → patkit-0.22.2}/patkit/configuration/configuration_models.py +0 -0
  40. {patkit-0.22.1 → patkit-0.22.2}/patkit/configuration/configuration_parsers.py +0 -0
  41. {patkit-0.22.1 → patkit-0.22.2}/patkit/configuration/exclusion_list_functions.py +0 -0
  42. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/AAA_raw_ultrasound.py +0 -0
  43. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/AAA_recordings.py +0 -0
  44. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/AAA_splines.py +0 -0
  45. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/RASL_3D_ultrasound_recordings.py +0 -0
  46. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/__init__.py +0 -0
  47. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/audio.py +0 -0
  48. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/session_import_config.py +0 -0
  49. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/spline_import_config.py +0 -0
  50. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/three_dim_ultrasound.py +0 -0
  51. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/video.py +0 -0
  52. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_import/wav_recordings.py +0 -0
  53. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_loader.py +0 -0
  54. {patkit-0.22.1 → patkit-0.22.2}/patkit/data_processor.py +0 -0
  55. {patkit-0.22.1 → patkit-0.22.2}/patkit/default_configuration/patkit-data.yaml +0 -0
  56. {patkit-0.22.1 → patkit-0.22.2}/patkit/default_configuration/patkit-gui.yaml +0 -0
  57. {patkit-0.22.1 → patkit-0.22.2}/patkit/default_configuration/patkit-publish.yaml +0 -0
  58. {patkit-0.22.1 → patkit-0.22.2}/patkit/default_configuration/patkit_logging_configuration.json +0 -0
  59. {patkit-0.22.1 → patkit-0.22.2}/patkit/errors.py +0 -0
  60. {patkit-0.22.1 → patkit-0.22.2}/patkit/export/__init__.py +0 -0
  61. {patkit-0.22.1 → patkit-0.22.2}/patkit/export/images.py +0 -0
  62. {patkit-0.22.1 → patkit-0.22.2}/patkit/export/metadata.py +0 -0
  63. {patkit-0.22.1 → patkit-0.22.2}/patkit/export/modality_data.py +0 -0
  64. {patkit-0.22.1 → patkit-0.22.2}/patkit/external_class_extensions/__init__.py +0 -0
  65. {patkit-0.22.1 → patkit-0.22.2}/patkit/external_class_extensions/base_model_extensions.py +0 -0
  66. {patkit-0.22.1 → patkit-0.22.2}/patkit/external_class_extensions/enum_extensions.py +0 -0
  67. {patkit-0.22.1 → patkit-0.22.2}/patkit/get_modality_types.py +0 -0
  68. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/image_save_dialog.py +0 -0
  69. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/list_save_dialog.py +0 -0
  70. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/ofbrowse.py +0 -0
  71. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/player_controls.py +0 -0
  72. {patkit-0.22.1 → patkit-0.22.2}/patkit/gui/replace_dialog.py +0 -0
  73. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/__init__.py +0 -0
  74. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/avi_read.py +0 -0
  75. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/dicom_ultra_read.py +0 -0
  76. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/rasl_dat_to_wav.py +0 -0
  77. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/splines_read.py +0 -0
  78. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/ult_read.py +0 -0
  79. {patkit-0.22.1 → patkit-0.22.2}/patkit/import_formats/wav_read.py +0 -0
  80. {patkit-0.22.1 → patkit-0.22.2}/patkit/initialise.py +0 -0
  81. {patkit-0.22.1 → patkit-0.22.2}/patkit/interpolate_raw_uti.py +0 -0
  82. {patkit-0.22.1 → patkit-0.22.2}/patkit/interpreter.py +0 -0
  83. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/__init__.py +0 -0
  84. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/aggregate_image.py +0 -0
  85. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/calculate_aggregate_images.py +0 -0
  86. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/calculate_distance_matrices.py +0 -0
  87. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/calculate_intensity.py +0 -0
  88. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/calculate_pd.py +0 -0
  89. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/calculate_spline_metric.py +0 -0
  90. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/distance_matrix.py +0 -0
  91. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/downsample_metric.py +0 -0
  92. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/intensity.py +0 -0
  93. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/metrics_helpers.py +0 -0
  94. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/ofreg.py +0 -0
  95. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/pd.py +0 -0
  96. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/spline_metric.py +0 -0
  97. {patkit-0.22.1 → patkit-0.22.2}/patkit/metrics/tongue_shape_analysis.py +0 -0
  98. {patkit-0.22.1 → patkit-0.22.2}/patkit/modalities/__init__.py +0 -0
  99. {patkit-0.22.1 → patkit-0.22.2}/patkit/modalities/recorded_metadata_classes.py +0 -0
  100. {patkit-0.22.1 → patkit-0.22.2}/patkit/modalities/recorded_modalities.py +0 -0
  101. {patkit-0.22.1 → patkit-0.22.2}/patkit/modalities/splines.py +0 -0
  102. {patkit-0.22.1 → patkit-0.22.2}/patkit/plot_and_publish/legend_generation.py +0 -0
  103. {patkit-0.22.1 → patkit-0.22.2}/patkit/plot_and_publish/plot.py +0 -0
  104. {patkit-0.22.1 → patkit-0.22.2}/patkit/plot_and_publish/plot_utilities.py +0 -0
  105. {patkit-0.22.1 → patkit-0.22.2}/patkit/plot_and_publish/publish.py +0 -0
  106. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/__init__.py +0 -0
  107. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/contour_tools.py +0 -0
  108. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/metric_calculations.py +0 -0
  109. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/perturbation_series_plots.py +0 -0
  110. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/rays_on_contours.py +0 -0
  111. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/simulation_datastructures.py +0 -0
  112. {patkit-0.22.1 → patkit-0.22.2}/patkit/simulation/simulation_plots.py +0 -0
  113. {patkit-0.22.1 → patkit-0.22.2}/patkit/ui_callbacks.py +0 -0
  114. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/__init__.py +0 -0
  115. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/logging_helpers.py +0 -0
  116. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/path.py +0 -0
  117. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/processing_helpers.py +0 -0
  118. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/string_formatters.py +0 -0
  119. {patkit-0.22.1 → patkit-0.22.2}/patkit/utility_functions/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patkit
3
- Version: 0.22.1
3
+ Version: 0.22.2
4
4
  Summary: Phonetic Analysis ToolKit: Tools for processing phonetic data
5
5
  Project-URL: bug_tracker, https://github.com/giuthas/patkit/issues
6
6
  Project-URL: changelog, https://github.com/giuthas/patkit/blob/main/docs/Release_notes.markdown
@@ -715,6 +715,7 @@ Requires-Dist: sounddevice>=0.5.5
715
715
  Requires-Dist: strictyaml>=1.7.3
716
716
  Description-Content-Type: text/markdown
717
717
 
718
+ [![test](https://github.com/giuthas/patkit/actions/workflows/test.yaml/badge.svg)](https://github.com/giuthas/patkit/actions/workflows/test.yaml)
718
719
  # PATKIT - Phonetic Analysis ToolKIT
719
720
 
720
721
  ![PATKIT GUI](https://giuthas.github.io/patkit/PATKIT_UI_0.22.0.png)
@@ -748,10 +749,7 @@ thesis](https://eresearch.qmu.ac.uk/handle/20.500.12289/10163).
748
749
  - See [uv](https://docs.astral.sh/uv/#getting-started) for more details.
749
750
  - On the commandline run `uv tool install patkit`
750
751
  - Run `patkit --help` for instructions.
751
- - If you want to run the example data, get the `recorded_data` and `scenarios` folders from
752
- [github](https://github.com/giuthas/patkit).
753
- - Run `patkit scenarios/minimal` in the folder where you downloaded the data
754
- and experiment from there.
752
+ - If you want to run the example data, see 'What's included' below for instructions.
755
753
 
756
754
  - If on Linux of the debian variety (ubuntu, popos, others), you may also need
757
755
  to run the following:
@@ -785,8 +783,9 @@ contents of the directory.
785
783
  Place the unzipped directories in the same directory and after installing
786
784
  patkit try running
787
785
  ```
788
- patkit scenarios minimal
786
+ patkit scenarios/minimal
789
787
  ```
788
+ and experiment from there.
790
789
 
791
790
  TODO 1.0: Move the data elsewhere to be optionally loaded.
792
791
 
@@ -1,3 +1,4 @@
1
+ [![test](https://github.com/giuthas/patkit/actions/workflows/test.yaml/badge.svg)](https://github.com/giuthas/patkit/actions/workflows/test.yaml)
1
2
  # PATKIT - Phonetic Analysis ToolKIT
2
3
 
3
4
  ![PATKIT GUI](https://giuthas.github.io/patkit/PATKIT_UI_0.22.0.png)
@@ -31,10 +32,7 @@ thesis](https://eresearch.qmu.ac.uk/handle/20.500.12289/10163).
31
32
  - See [uv](https://docs.astral.sh/uv/#getting-started) for more details.
32
33
  - On the commandline run `uv tool install patkit`
33
34
  - Run `patkit --help` for instructions.
34
- - If you want to run the example data, get the `recorded_data` and `scenarios` folders from
35
- [github](https://github.com/giuthas/patkit).
36
- - Run `patkit scenarios/minimal` in the folder where you downloaded the data
37
- and experiment from there.
35
+ - If you want to run the example data, see 'What's included' below for instructions.
38
36
 
39
37
  - If on Linux of the debian variety (ubuntu, popos, others), you may also need
40
38
  to run the following:
@@ -68,8 +66,9 @@ contents of the directory.
68
66
  Place the unzipped directories in the same directory and after installing
69
67
  patkit try running
70
68
  ```
71
- patkit scenarios minimal
69
+ patkit scenarios/minimal
72
70
  ```
71
+ and experiment from there.
73
72
 
74
73
  TODO 1.0: Move the data elsewhere to be optionally loaded.
75
74
 
@@ -85,7 +85,7 @@ class ConfigPaths:
85
85
 
86
86
  class Configuration:
87
87
  """
88
- Main configuration class of patkit.
88
+ Main configuration class of patkit.
89
89
  """
90
90
 
91
91
  # TODO: implement save functionality.
@@ -95,7 +95,7 @@ class Configuration:
95
95
  configuration_paths: ConfigPaths
96
96
  ) -> None:
97
97
  """
98
- Init the main configuration object.
98
+ Init the main configuration object.
99
99
 
100
100
  Run only once. Updates should be done with methods of the class.
101
101
 
@@ -113,7 +113,8 @@ class Configuration:
113
113
  self._data_yaml = load_data_params(
114
114
  self._config_paths.data_config)
115
115
  self._data_config = DataConfig(**self._data_yaml.data)
116
- recorded = self._config_paths.path/self._data_config.recorded_data_path
116
+ recorded = \
117
+ self._config_paths.path/self._data_config.recorded_data_path
117
118
  self._data_config.recorded_data_path = recorded.resolve()
118
119
  else:
119
120
  self._data_config = None
@@ -49,6 +49,7 @@ from patkit.external_class_extensions import (
49
49
  # TODO 1.0: Decouple program and file format versions at version 1.0.
50
50
  PATKIT_VERSION = version('patkit')
51
51
  PATKIT_FILE_VERSION = PATKIT_VERSION
52
+ EXERCISE_FILE_VERSION = PATKIT_VERSION
52
53
 
53
54
  DEFAULT_ENCODING = 'utf-8'
54
55
 
@@ -192,6 +193,7 @@ class PatkitConfigFile(str, Enum):
192
193
  SESSION: how PATKIT should read a session based on recorded data
193
194
  SPLINE: spline formatting
194
195
  """
196
+ ANSWER = "patkit-answer.yaml"
195
197
  EXERCISE = "patkit-exercise.yaml"
196
198
  DATA = "patkit-data.yaml"
197
199
  GUI = "patkit-gui.yaml"
@@ -208,6 +210,21 @@ class PatkitConfigFile(str, Enum):
208
210
  return self.value
209
211
 
210
212
 
213
+ class PatkitDirectory(str, Enum):
214
+ """
215
+ Standard directory names used in PATKIT.
216
+ """
217
+ ANSWERS = "answers"
218
+ EXAMPLE = "example"
219
+ EXERCISE = "exercise"
220
+
221
+ def __str__(self) -> str:
222
+ """
223
+ Ensure f-strings and print() output the string value.
224
+ """
225
+ return self.value
226
+
227
+
211
228
  class PatkitSuffix(str, Enum):
212
229
  """
213
230
  Suffixes for files saved by patkit.
@@ -231,7 +248,7 @@ class SavedObjectTypes(Enum):
231
248
  """
232
249
  Represent type of a saved patkit object in .meta.
233
250
  """
234
- # TODO 1.0: Check if this is actually in use.
251
+ EXERCISE = "Exercise"
235
252
  DATASET = "Dataset"
236
253
  MODALITY = "Modality"
237
254
  RECORDING = "Recording"
@@ -454,3 +471,49 @@ class ExerciseMode(ListablePrintableEnum, metaclass=ValueComparedEnumMeta):
454
471
  """
455
472
  ANSWER = "Answer"
456
473
  EXAMPLE = "Example"
474
+
475
+
476
+ class ExerciseScrambler(
477
+ ListablePrintableEnum, metaclass=ValueComparedEnumMeta
478
+ ):
479
+ """
480
+ Ways of scrambling boundaries for an Exercise.
481
+ """
482
+ EQUIDISTANT = "Equidistant"
483
+
484
+
485
+ class DefaultCanvasColors(str, Enum):
486
+ """
487
+ Default background colors for the Matplotlib figure canvas.
488
+
489
+ These represent the hardcoded default theme colors for different
490
+ annotator and exercise modes until config overrides are implemented.
491
+ """
492
+ ANNOTATOR_LIGHT = "white"
493
+ ANNOTATOR_DARK = "black"
494
+
495
+ EXAMPLE_LIGHT = "#e7eaff"
496
+ EXAMPLE_DARK = "#000212"
497
+
498
+ ANSWER_LIGHT = "#e6ffe9"
499
+ ANSWER_DARK = "#001202"
500
+
501
+ def __str__(self) -> str:
502
+ """
503
+ Ensure f-strings and print() output the string value.
504
+ """
505
+ return self.value
506
+
507
+
508
+ class DefaultCursorColors(str, Enum):
509
+ """
510
+ Default colors for GUI cursor and selection elements.
511
+ """
512
+ SELECTION = "deepskyblue"
513
+ PLAYBACK = "red"
514
+
515
+ def __str__(self) -> str:
516
+ """
517
+ Ensure f-strings and print() output the string value.
518
+ """
519
+ return self.value
@@ -43,7 +43,7 @@ from .data_structures import (
43
43
  Exercise, Answer, Manifest, Modality, Recording, Session
44
44
  )
45
45
  from .metadata_classes import (
46
- FileInformation,
46
+ AnswerMetadata, ExerciseMetadata, FileInformation,
47
47
  ModalityData, ModalityMetaData, PointAnnotations,
48
48
  RecordingMetaData, StatisticMetaData
49
49
  )
@@ -385,7 +385,7 @@ class AbstractDataContainer(AbstractDataObject):
385
385
  """
386
386
  Abstract baseclass for Recording, Session, and DataSet.
387
387
 
388
- This class collects behaviors that are shared by the data base classes i.e.
388
+ This class collects behaviors that are shared by the database classes i.e.
389
389
  classes which collect DataContainers and/or AbstractDataContainers.
390
390
  """
391
391
 
@@ -32,20 +32,6 @@
32
32
  """PATKIT's main datastructures."""
33
33
 
34
34
  from __future__ import annotations
35
- from .metadata_classes import (
36
- FileInformation, ModalityData, ModalityMetaData, PointAnnotations,
37
- RecordingMetaData
38
- )
39
- from .base_classes import AbstractDataContainer, AbstractData, Statistic
40
- from patkit.patgrid import PatGrid
41
- from patkit.utility_functions import stem_path
42
- from patkit.errors import (
43
- DimensionMismatchError, MissingDataError, OverwriteError
44
- )
45
- from patkit.constants import AnnotationType, SourceSuffix
46
- from patkit.configuration import SessionConfig
47
- import textgrids
48
- import numpy as np
49
35
 
50
36
  import abc
51
37
  import logging
@@ -54,11 +40,30 @@ from copy import deepcopy
54
40
  from pathlib import Path
55
41
  from textwrap import indent
56
42
 
43
+ import textgrids
44
+ import numpy as np
45
+
46
+ from patkit.configuration import SessionConfig
47
+ from patkit.constants import (
48
+ AnnotationType, PatkitDirectory, SourceSuffix
49
+ )
50
+ from patkit.errors import (
51
+ DimensionMismatchError, MissingDataError, OverwriteError
52
+ )
53
+ from patkit.patgrid import PatGrid
54
+ from patkit.utility_functions import stem_path
55
+
56
+ from .metadata_classes import (
57
+ AnswerMetadata, ExerciseMetadata, FileInformation,
58
+ ModalityData, ModalityMetaData, PointAnnotations,
59
+ RecordingMetaData
60
+ )
61
+ from .base_classes import AbstractDataContainer, AbstractData, Statistic
57
62
 
58
63
  _logger = logging.getLogger('patkit.data_structures')
59
64
 
60
65
 
61
- class Answer(UserList):
66
+ class Answer(AbstractDataContainer, UserList):
62
67
  """
63
68
  Answer is an answer to an Exercise and consists of PatGrids.
64
69
 
@@ -69,23 +74,42 @@ class Answer(UserList):
69
74
  self,
70
75
  container: Exercise,
71
76
  scenario: Session,
72
- scramble: bool,
73
- name: str = "",
74
- author: str = "",
77
+ metadata: AnswerMetadata,
78
+ name: str,
75
79
  cursor: int = 0,
80
+ file_info: FileInformation | None = None,
76
81
  ):
77
- super().__init__()
82
+ """
83
+ Initialize an Answer object containing PatGrids for an Exercise.
84
+
85
+ Parameters
86
+ ----------
87
+ container : Exercise
88
+ The Exercise this Answer belongs to.
89
+ scenario : Session
90
+ The Session containing the recordings.
91
+ name : str
92
+ The name of the answer.
93
+ cursor : int
94
+ The cursor index pointing to the active recording.
95
+ file_info : FileInformation | None
96
+ The FileInformation metadata for the answer.
97
+ """
98
+ if file_info is None:
99
+ file_info = FileInformation()
100
+
101
+ super().__init__(name=name, metadata=metadata,
102
+ container=container, file_info=file_info)
78
103
  self.container = container
79
104
  self.scenario = scenario
80
- self.name = name
81
- self.author = author
82
105
  self.cursor = cursor
83
106
 
84
107
  for recording in self.scenario:
85
108
  patgrid = deepcopy(recording.patgrid)
86
109
  self.append(patgrid)
87
110
 
88
- if scramble:
111
+ # TODO: this will probably break loading edited answers
112
+ if self.metadata.scramble:
89
113
  for patgrid in self:
90
114
  for tier in patgrid:
91
115
  patgrid[tier].scramble()
@@ -100,6 +124,20 @@ class Answer(UserList):
100
124
  representation += indent(text=patgrid_string, prefix="\t")
101
125
  return representation
102
126
 
127
+ @property
128
+ def patkit_data_path(self) -> Path:
129
+ """Dynamically compute the path to the answer directory."""
130
+ # Route example answers directly to the example directory
131
+ if (
132
+ self.container.example is not None and
133
+ self is self.container.example
134
+ ):
135
+ return self.container.patkit_path / PatkitDirectory.EXAMPLE
136
+
137
+ return (
138
+ self.container.patkit_path / PatkitDirectory.ANSWERS / self.name
139
+ )
140
+
103
141
  def current(self) -> PatGrid:
104
142
  """
105
143
  The PatGrid at the current index.
@@ -151,7 +189,7 @@ class Answer(UserList):
151
189
  return self.cursor
152
190
 
153
191
 
154
- class Exercise(UserDict):
192
+ class Exercise(AbstractDataContainer, UserDict):
155
193
  """
156
194
  Exercise is list of Answers relating to a Scenario (Session).
157
195
 
@@ -162,24 +200,46 @@ class Exercise(UserDict):
162
200
  def __init__(
163
201
  self,
164
202
  scenario: Session,
203
+ name: str,
204
+ metadata: ExerciseMetadata,
165
205
  answers: list[Answer] | None = None,
166
206
  example: dict[str, Answer] | None = None,
167
207
  index: int = 0,
208
+ file_info: FileInformation | None = None,
168
209
  ):
169
- super().__init__()
210
+ """
211
+ Initialize an Exercise.
212
+
213
+ Parameters
214
+ ----------
215
+ scenario : Session
216
+ The Session containing the recordings for this exercise.
217
+ answers : list[Answer] | None
218
+ A list of Answer objects to populate the exercise.
219
+ example : dict[str, Answer] | None
220
+ The example scrambled Answer to present to the user.
221
+ index : int
222
+ The cursor index.
223
+ """
224
+ super().__init__(name=name, metadata=metadata, file_info=file_info)
170
225
  if example is None:
171
226
  _logger.debug("Creating an example answer")
227
+ metadata = AnswerMetadata(
228
+ scramble=False,
229
+ )
172
230
  self.example = Answer(
173
231
  container=self,
174
232
  scenario=scenario,
175
- scramble=False,
233
+ metadata=metadata,
234
+ name=PatkitDirectory.EXAMPLE,
176
235
  )
177
236
  else:
178
237
  self.example = example
179
238
  self.scenario = scenario
180
239
 
181
240
  if answers is not None:
182
- self.update(answers)
241
+ for answer in answers:
242
+ self[answer.name] = answer
183
243
 
184
244
  self.cursor = index
185
245
 
@@ -202,6 +262,14 @@ class Exercise(UserDict):
202
262
  def current_answer(self) -> Answer:
203
263
  return self[list(self.keys())[self.cursor]]
204
264
 
265
+ @property
266
+ def patkit_path(self) -> Path | None:
267
+ return self.file_info.patkit_path
268
+
269
+ @patkit_path.setter
270
+ def patkit_path(self, path: Path) -> None:
271
+ self.file_info.patkit_path = path
272
+
205
273
  def new_blank_answer(
206
274
  self,
207
275
  cursor: int = 0,
@@ -218,7 +286,7 @@ class Exercise(UserDict):
218
286
  name : str | None
219
287
  Name of the new Answer, by default None. If None is passed the new
220
288
  name will be autogenerated and have the form
221
- `f"Answer {len(self)+1}"`.
289
+ `f"Answer_{len(self)+1}"`.
222
290
  author : str
223
291
  Name of the new Answer's author, by default an empty string.
224
292
  """
@@ -226,15 +294,19 @@ class Exercise(UserDict):
226
294
 
227
295
  # TODO 1.0: Is this the best place to name answers?
228
296
  if name is None:
229
- name = f"Answer {len(self)+1}"
297
+ name = f"Answer_{len(self)+1}"
230
298
 
299
+ metadata = AnswerMetadata(
300
+ scramble=True,
301
+ author=author
302
+ )
231
303
  blank = Answer(
232
304
  container=self,
233
305
  scenario=self.scenario,
234
- scramble=True,
306
+ name=name,
307
+ metadata=metadata,
235
308
  cursor=cursor,
236
309
  )
237
- name = f"Answer {len(self)+1}"
238
310
  self[name] = blank
239
311
 
240
312
 
@@ -336,11 +408,15 @@ class Session(AbstractDataContainer, UserList):
336
408
  config: SessionConfig,
337
409
  file_info: FileInformation,
338
410
  recordings: list[Recording] | None = None,
339
- statistics: dict[str, Statistic] | None = None
411
+ statistics: dict[str, Statistic] | None = None,
412
+ exercise: Exercise | None = None,
340
413
  ) -> None:
341
414
  super().__init__(
342
- container=None, name=name, metadata=config,
343
- file_info=file_info, statistics=statistics)
415
+ container=None,
416
+ name=name,
417
+ metadata=config,
418
+ file_info=file_info,
419
+ statistics=statistics)
344
420
 
345
421
  if recordings is not None:
346
422
  for recording in recordings:
@@ -349,6 +425,7 @@ class Session(AbstractDataContainer, UserList):
349
425
  self.extend(recordings)
350
426
 
351
427
  self.config = config
428
+ self.exercise = exercise
352
429
 
353
430
  def __repr__(self) -> str:
354
431
  representation = "Session:\n"
@@ -42,7 +42,7 @@ from pydantic import PositiveInt
42
42
  from patkit.configuration import (
43
43
  PointAnnotationParams
44
44
  )
45
- from patkit.constants import AnnotationType
45
+ from patkit.constants import AnnotationType, ExerciseScrambler
46
46
  from patkit.external_class_extensions import PatkitBaseModel
47
47
  from patkit.utility_functions import (
48
48
  is_sequence_form, stem_path
@@ -116,6 +116,54 @@ class FileInformation:
116
116
  return stem_path(basepath)
117
117
 
118
118
 
119
+ class AnswerMetadata(PatkitBaseModel):
120
+ """
121
+ Metadata of an Exercise Answer.
122
+
123
+ Parameters
124
+ ----------
125
+ scramble : bool
126
+ Whether to scramble the PatGrids upon creation.
127
+ author : str
128
+ The author/user of the answer.
129
+ time_created : str | None
130
+ ISO formatted timestamp of when this answer was created.
131
+ time_last_edited : str | None
132
+ ISO formatted timestamp of when this answer was last edited.
133
+ """
134
+ scramble: bool
135
+ author: str = ""
136
+ time_created: str | None = None
137
+ time_last_edited: str | None = None
138
+
139
+ def model_post_init(self, __context):
140
+ now = datetime.now().isoformat()
141
+ if self.time_created is None:
142
+ self.time_created = now
143
+ if self.time_last_edited is not None:
144
+ self.time_last_edited = now
145
+
146
+
147
+ class ExerciseMetadata(PatkitBaseModel):
148
+ """
149
+ Metadata for an Exercise.
150
+
151
+ Parameters
152
+ ----------
153
+ time_created : str | None
154
+ ISO formatted timestamp of when this exercise was created.
155
+ scrambling_method : str
156
+ The method used to scramble the exercise TextGrids.
157
+ """
158
+ time_created: str | None = None
159
+ scrambling_method: ExerciseScrambler = ExerciseScrambler.EQUIDISTANT
160
+
161
+ def model_post_init(self, __context):
162
+ now = datetime.now().isoformat()
163
+ if self.time_created is None:
164
+ self.time_created = now
165
+
166
+
119
167
  @dataclass
120
168
  class ModalityData:
121
169
  """
@@ -32,9 +32,12 @@
32
32
  """GUI elements."""
33
33
 
34
34
  from .annotator_window import UiMainWindow
35
+ from .audio_player import AudioPlayer
35
36
  from .boundary_animation import BoundaryAnimator
37
+ from .exercise_dialogs import NewAnswerDialog, NewExerciseDialog
36
38
  from .image_save_dialog import ImageSaveDialog
37
39
  from .list_save_dialog import ListSaveDialog
38
40
  from .list_selection_dialog import ListSelectionDialog
39
41
  from .player_controls import PlayerControls
42
+ from .plot_controller import PlotController
40
43
  from .replace_dialog import ReplaceDialog