Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 config/profiles/explanatory/pipeline_definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pipeline:

Training improves alignment accuracy by adapting models to your speakers, recording conditions, or dialect. Requires a dataset that already has alignments (from a pretrained model) to use as training data.

*Skip this step if using pretrained models is sufficient for your needs.*
*Train Aligners is unavailable in the left sidebar until a dataset with manual alignments is registered.*

---

Expand Down
4 changes: 4 additions & 0 deletions src/voxkit/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ def build_feedback_mailto_url(
QListWidget::item:hover {
background-color: #b0cef2;
}
QListWidget::item:disabled {
color: #a8a8a8;
background-color: transparent;
}
QWidget#centralWidget {
background-color: #f5f7fa;
}
Expand Down
37 changes: 36 additions & 1 deletion src/voxkit/gui/pages/pipeline/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def on_settings(self):
QHBoxLayout,
QLabel,
QListWidget,
QListWidgetItem,
QPushButton,
QScrollArea,
QVBoxLayout,
Expand All @@ -92,6 +93,7 @@ def on_settings(self):

from voxkit.gui.components import AnimatedStackedWidget
from voxkit.gui.styles import Buttons, Containers, Labels
from voxkit.storage import datasets

if TYPE_CHECKING:
from voxkit.config.pipeline_config import PipelineConfig
Expand Down Expand Up @@ -164,13 +166,18 @@ def init_ui(self):
# Store mapping of menu index to stacker for reload
self.stacker_instances = []

# Store menu items by step id so availability can be toggled later
self._menu_items: dict[str, QListWidgetItem] = {}

# Right side - Stacked widget for different pipeline pages
self.stacked_widget = AnimatedStackedWidget()

# Dynamically create menu items and stackers from configuration
for step in self.config.enabled_steps:
# Add menu item
self.menu_list.addItem(step.label)
menu_item = QListWidgetItem(step.label)
self.menu_list.addItem(menu_item)
self._menu_items[step.id] = menu_item

# Get the stacker class from registry
stacker_class = STACKER_REGISTRY.get(step.stacker_class)
Expand Down Expand Up @@ -264,6 +271,8 @@ def toggle():
self.menu_list.currentRowChanged.connect(self.change_page)
self.menu_list.setCurrentRow(0)

self._refresh_training_availability()

def reload(self):
"""Reload models and datasets in the pipeline pages.

Expand Down Expand Up @@ -303,6 +312,32 @@ def reload(self):
if hasattr(stacker_widget, "reload_datasets"):
stacker_widget.reload_datasets()

self._refresh_training_availability()

def _refresh_training_availability(self):
"""Gray out the Train Aligners step unless a dataset has a manual alignment.

Training only produces a meaningful model when it can learn from a
hand-corrected alignment, so this step is disabled until at least one
dataset qualifies. Why it's disabled is explained in the Ⓑ Train
Aligners section of the Pipeline Overview, not in the sidebar itself
(too narrow for an inline explanation).
"""
menu_item = self._menu_items.get("training")
stacker_widget = next(
(widget for step_id, _, widget in self.stacker_instances if step_id == "training"),
None,
)
if menu_item is None or stacker_widget is None:
return

if datasets.any_dataset_has_manual_alignments():
menu_item.setFlags(menu_item.flags() | Qt.ItemFlag.ItemIsEnabled)
stacker_widget.setEnabled(True)
else:
menu_item.setFlags(menu_item.flags() & ~Qt.ItemFlag.ItemIsEnabled)
stacker_widget.setEnabled(False)

def change_page(self, index):
"""Change the displayed page based on menu selection with animation"""
if index >= 0: # Valid index
Expand Down
9 changes: 9 additions & 0 deletions src/voxkit/gui/pages/pipeline/training_stacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ def reload_datasets(self):

def build_ui(self):
"""Build the training UI."""
note_label = QLabel(
"Note: training only makes sense for datasets with manual (hand-corrected) "
"alignments — models trained on machine-generated alignments alone tend to "
"just reinforce whatever errors are already there."
)
note_label.setWordWrap(True)
note_label.setStyleSheet(Labels.INFO)
self.content_layout.addWidget(note_label)

# Model Selection Panel
engines_dict = {
engine_id: engine
Expand Down
10 changes: 10 additions & 0 deletions src/voxkit/storage/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- **create_dataset**: Create a new dataset with metadata and directories
- **get_dataset_metadata**: Retrieve metadata for a specific dataset
- **list_datasets_metadata**: List all existing datasets
- **any_dataset_has_manual_alignments**: Whether any dataset has a hand alignment
- **update_dataset_metadata**: Update metadata fields for a specific dataset
- **delete_dataset**: Delete a registered dataset and its metadata
- **export_dataset**: Export a dataset to a specified output path
Expand Down Expand Up @@ -321,6 +322,15 @@ def list_datasets_metadata() -> List[DatasetMetadata]:
return []


def any_dataset_has_manual_alignments() -> bool:
"""Whether at least one registered dataset has a hand/manual alignment.

Training only produces a meaningful model when it can learn from a
manually-corrected alignment, so this gates the Train Aligners step.
"""
return any(d.get("hand_alignments_path") for d in list_datasets_metadata())


def update_dataset_metadata(
dataset_id: str,
updates: dict,
Expand Down
Loading