Files
DP44/enriched-qwen3-coder-next/DataPRO/Modules/RegionOfInterest/RegionOfInterestChannels/ViewModel.md
2026-04-17 14:55:32 -04:00

17 KiB
Raw Blame History

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
DataPRO/Modules/RegionOfInterest/RegionOfInterestChannels/ViewModel/RegionOfInterestChannelsViewModel.cs
2026-04-16T04:34:24.697647+00:00 Qwen/Qwen3-Coder-Next-FP8 1 2e798c0127d02e4a

ViewModel

Documentation: RegionOfInterestChannelsViewModel


1. Purpose

This module implements the RegionOfInterestChannelsViewModel, a core view model for managing and displaying channels associated with Regions of Interest (ROIs) in the DTS test configuration and analysis system. It serves as the data context for the ROI Channels UI, enabling users to view, filter, sort, and select channels for inclusion in one or more ROIs. The view model bridges UI interactions with underlying channel data (from IGroupChannel, ITestChannel, or test metadata), handles ROI channel assignment validation (e.g., scrubbing stale channel references), and coordinates with the event aggregator for cross-component communication (e.g., busy indicators, notifications). It is used in contexts such as ROI definition, CSV/HDF export, and data download workflows.


2. Public Interface

Constructor

RegionOfInterestChannelsViewModel(
    IRegionOfInterestChannelsView view,
    Prism.Regions.IRegionManager regionManager,
    Prism.Events.IEventAggregator eventAggregator,
    Unity.IUnityContainer unityContainer)
  • Initializes the view model, sets up data binding, registers event subscriptions (RaiseNotification, BusyIndicatorChangeNotification), and initializes RegionsOfInterest as a BindingList<IRegionOfInterest>.

Properties

  • bool IsDirty { get; private set; }
    Indicates whether the underlying data has unsaved changes. Currently always false in source.

  • bool IsBusy { get; set; }
    Binds to UI busy indicator state. Set via OnBusyIndicatorNotification.

  • bool IsMenuIncluded { get; set; }

  • bool IsNavigationIncluded { get; set; }
    UI layout flags for optional navigation elements.

  • IRegionOfInterestChannelsView View { get; set; }
    Reference to the associated view.

  • InteractionRequest<Notification> NotificationRequest { get; }

  • InteractionRequest<Confirmation> ConfirmationRequest { get; }
    Prism Interactivity triggers for modal dialogs.

  • BindingList<IRegionOfInterest> RegionsOfInterest { get; set; }
    List of ROI definitions. On setter, performs scrubbing of stale channel references (see Gotchas), then calls ResetDataView().

  • List<DTS.Common.Classes.Groups.GroupChannel> AllChannelsUnfiltered { get; set; }
    Raw list of all channels (from IGroupChannel[]) before filtering.

  • ObservableCollection<GroupChannel> AllChannels { get; set; }
    Filtered list of GroupChannel instances displayed in the UI.

  • ObservableCollection<ITestChannel> AllTestChannels { get; set; }
    Filtered list of ITestChannel instances (used when loading from test summary/metadata).

  • string[] AllChannelSSNs { get; }
    Returns array of Hardware\SerialNumber strings for all enabled channels (used for ROI export). Handles both GroupChannel and ITestChannel sources.

  • BindingList<ChannelEnabler> ChannelList { get; set; }
    List of ChannelEnabler objects representing UI rows (one per channel), each containing ROI inclusion checkboxes (ROIIncludes). Setter wires ListChanged event.

  • ObservableCollection<ColumnDescriptor> Columns { get; set; }
    Defines grid column metadata (header, display member, type) for ChannelList.

  • IsoViewMode ISOViewMode { get; set; }
    Controls which channel name variant is used (e.g., ISO, User Code, or raw).

Methods

  • void SetParent(object o)
    Stores Parent object (used in event args for ROI channel selection events).

  • void SetTest(string path, IsoViewMode viewMode)
    Loads test metadata from .dts file at path, populates _testSummary, and triggers Filter() and ResetDataView().

  • void SetGroups(ITestSetup testSetup, Dictionary<string, IDASHardware> serialNumberToHardware, IsoViewMode viewMode)
    Initializes channel data from ITestSetup (e.g., during ROI definition). Validates DAS assignments, logs phantom assignments, sorts channels, and calls ProcessChannels()Filter()ResetDataView().

  • void Filter(string term) / void SetFilter(PossibleFilters bridgeFilter) / void Filter(object tag, string term)
    Set search term, filter type, or field-specific filter term, then invoke Filter().

  • void ClearAllFilters()
    Clears _filterByField dictionary.

  • void Sort(object o, bool bColumnClick)
    Sorts ChannelList by field (e.g., GroupName, SerialNumber, or ROIIncludes[i]). Supports toggling sort direction on repeated clicks.

  • void SelectAll(int roiIndex, bool selection)
    Sets Checked state of all ROIIncludes[roiIndex] checkboxes to selection.

  • void Activated() / void Cleanup() / Task CleanupAsync() / void Initialize() / Task InitializeAsync()
    Lifecycle stubs (no-op in source).

  • bool Validate(ref List<string> errors)
    Currently always returns true.

  • void OnPropertyChanged(string propertyName)
    Raises PropertyChanged event.


3. Invariants

  • Channel scrubbing on ROI assignment change: When RegionsOfInterest is set, stale channel references (channels no longer present in AllChannelsUnfiltered) are removed from roi.ChannelNames and roi.ChannelIds. This is done by comparing against AllChannelHash (computed from AllChannelsUnfiltered with hardware/serial number normalization) and allChannelIdHash.

  • Channel ID parsing: Older .dts files may have composite ChannelId strings (e.g., "H3-3ch_0_2"). ParseChannelId() extracts the trailing numeric ID; if parsing fails, returns -1.

  • Digital outputs excluded: In BuildChannelListFromGroupChannels(), channels with sd.IsDigitalOutput() or sd.IsTestSpecificDigitalOutput() are skipped.

  • Filtering semantics:

    • ChannelFilter() uses AND logic across multiple filter fields (a channel must match all active filters).
    • ChannelSearch() uses OR logic (a channel matches if any field contains the search term).
    • ROIIncludes is excluded from search (commented as "not meaningful").
  • Hardware/serial normalization: Channel descriptors use RegionOfInterest.RemoveParentDASName() and RegionOfInterest.RemoveAssignedByIDFromHardwareString() to normalize hardware strings.

  • Calibration caching: BuildChannelListFromGroupChannels() caches sensor calibrations (cals) to avoid repeated DB lookups.


4. Dependencies

Imports/Usings (External Dependencies)

  • Prism: IRegionManager, IEventAggregator, InteractionRequest<T>, PopupWindowAction (via NotificationRequest).
  • Unity: IUnityContainer.
  • DTS Common Libraries:
    • DTS.Common.Classes.Groups, DTS.Common.Enums, DTS.Common.Events.*, DTS.Common.Interface.*, DTS.Common.Utils, DTS.Common.Utilities.Logging.APILogger.
    • DTS.SensorDB (for SensorsCollection, SensorCalibrationList).
    • DTS.Serialization.SliceRaw (for PersistentChannel.GetIsoCode).
  • System: BindingList<T>, ObservableCollection<T>, INotifyPropertyChanged, IComparer<T>.

Internal Dependencies

  • Interfaces:
    • IRegionOfInterestChannelsView, IRegionOfInterest, IGroupChannel, ITestChannel, IDASHardware, IGroup, ITestSetup, ITestSummary.
  • Helper Types:
    • ChannelSerialNumber, HardwareConstants, RegionOfInterest, DTS.Common.Constants.CURRENT_SUFFIX.
    • ChannelEnabler, State, ColumnDescriptor, ChannelEnablerComparer (defined in same file).
  • Events:
    • RaiseNotification, BusyIndicatorChangeNotification, RegionOfInterestChannelsSelectedEvent, PageErrorEvent.

Depended Upon By

  • UI layer (IRegionOfInterestChannelsView implementations).
  • Event subscribers (e.g., RegionOfInterestChannelsSelectedEvent listeners).
  • Export/download workflows (SetTest(), SetGroups()).

5. Gotchas

  • AllChannels null handling: AllChannelSSNs and BuildChannelListFromSummary() guard against AllChannels being null (e.g., when loaded via export tile), but BuildChannelListFromGroupChannels() does not check AllChannels for null before iteration (relies on ?? new ObservableCollection<>() in SetGroups()).

  • ROIIncludes index mismatch: In CheckboxesOnListChanged, LastIndexChanged is used to infer the ROI index, but this relies on the ListChanged events NewIndex matching the ROI index at the time of change. If ChannelList is reordered, this index may become stale.

  • ParseChannelId edge case: If parsedChannelId is non-numeric (e.g., "f9f0bfe8-afc4-4730-8045-8f1e45340573_0_8533""8533" is numeric, but "f9f0bfe8-afc4-4730-8045-8f1e45340573" is not), long.TryParse fails and returns -1. This may cause ROI channel assignment to silently drop channels.

  • UpdateChannelList heuristic: This method replaces a channel in channelList if its serial number matches any channel in AllChannelsUnfiltered. This may incorrectly overwrite channels if multiple channels share the same serial number (e.g., same sensor type on different hardware).

  • IsDirty unused: The property is defined but never set to true, making it unreliable for change tracking.

  • Calibration fallback: In BuildChannelListFromGroupChannels(), if sd.Calibration?.Records?.Records?[0]?.EngineeringUnits is null, "N/A" is used. However, ChannelFilter() for DisplayUnits uses a separate GetLatestCalibrationBySerialNumber(sd) call, which may yield different results if calibrations change mid-session.

  • ChannelSearch redundancy: ChannelSearch(GroupChannel) and ChannelSearch(ITestChannel) have overlapping logic, but ChannelSearch(GroupChannel) skips Fields.DisplayName (commented as "redundant"), while ChannelSearch(ITestChannel) does not.

  • ChannelEnablerComparer sort stability: Sorting is case-insensitive but not culture-aware (StringComparison.InvariantCultureIgnoreCase). May produce unexpected orderings for non-ASCII characters.

  • SetTest() assumes single test: SetTest() loads tsl[0] unconditionally; if tsl is empty, this will throw IndexOutOfRangeException.

  • ROIChannelEnabler unused: A ROIChannelEnabler class is defined but never instantiated or used in the source.

  • IsBusy thread affinity: OnBusyIndicatorNotification subscribes with ThreadOption.PublisherThread, but IsBusy setter calls OnPropertyChanged("IsBusy"), which may raise PropertyChanged on the publisher thread (UI thread). If IsBusy is set from a background thread elsewhere, this could cause cross-thread exceptions (though not evident in current usage).

  • AllChannelSSNs for CSV export: The comment //13477 Crash when clicking + to add second ROI for CSV export implies a known race condition or null-state issue when dynamically adding ROIs during export. The guard (null != AllChannels && AllChannels.Any()) mitigates but does not eliminate all failure modes.

  • No validation of ROI channel count: SelectAll(int roiIndex, bool selection) checks bounds on roiIndex, but no validation ensures roiIndex corresponds to a valid ROI in _regionsOfInterest after RegionsOfInterest is reassigned.

  • ChannelEnabler.LastIndexChanged reset: LastIndexChanged is set on ListChanged, but never reset. If multiple changes occur rapidly, it may retain an outdated index.

  • ChannelFilter for SampleRate: Uses ch.TestSampleRate.ToString() (no format specifier), while ChannelSearch uses both ToString() and ToString("N"). Inconsistent formatting may cause filter mismatches.

  • ChannelFilter for DisplayUnits: Uses GetLatestCalibrationBySerialNumber(sd) without caching, potentially causing repeated DB lookups in Filter() loops.

  • ChannelFilter for DASSerialNumber: Uses ch.HardwareChannel?.GetParentDAS().SerialNumber ?? "N/A", but ChannelFilter(ITestChannel) uses ch.HardwareChannelName directly (no GetParentDAS()), leading to inconsistent behavior.

  • ChannelEnabler.Descriptor format: Descriptor = chHardware + "\\" + serialNumber assumes chHardware and serialNumber are non-null; if either is null, this may produce "\\serial" or "hardware\\".

  • RegionOfInterestChannelsSelectedEvent publish: Published in CheckboxesOnListChanged after updating _regionsOfInterest[roiChangedIndex], but the event args use _regionsOfInterest[roiChangedIndex] after the list has been modified. If roiChangedIndex is stale (see index mismatch), this may publish stale ROI data.

  • SetGroups() phantom DAS assignment: Logs an error for ch.DASChannelIndex < 0, but continues processing (does not skip the channel). May lead to inconsistent channel sample rates.

  • BuildChannelListFromGroupChannels() sensor lookup: If sd is null after lookup, logs and skips the channel. However, ch.SensorData is not assigned in this case, so subsequent calls may repeat the lookup.

  • ChannelEnabler.GetChannelName(): Returns SerialNumber as fallback for missing UserChannelName/ISOChannelName, but AllChannelSSNs uses Hardware + "\\" + serialNumber. Inconsistent naming may cause mismatches in ROI channel matching.

  • ChannelEnablerComparer null safety: Compares aValue?.ToString() and bValue?.ToString(), but if both are null, returns 0 (equal). If only one is null, string.Compare(null, "x") returns -1, which may not be intuitive.

  • ChannelEnabler.ROIIncludes event wiring: ROIIncludes.ListChanged is wired in constructor, but ChannelEnabler instances are recreated on every ChannelList = ... assignment. The old BindingList<State> may not be disposed, potentially causing memory leaks if event handlers are not properly detached (though ListChanged is not explicitly unhooked in setter).

  • ChannelEnabler.LastIndexChanged property: Used only to trigger OnPropertyChanged("LastIndexChanged"), but no UI binding or logic consumes this property. Likely tech debt.

  • RegionOfInterestChannelsViewModel Parent field: Stored as object Parent (not typed), and passed in RegionOfInterestChannelsSelectedEventArgs. No validation ensures Parent is of expected type.

  • ChannelFilter for ROIIncludes field: The Fields.ROIIncludes case in ChannelFilter throws ArgumentOutOfRangeException. This field is not filterable (as intended), but the enum value is included in Enum.GetValues, so the switch must handle it (currently via default: throw).

  • ChannelSearch for SampleRate: Uses ch.SampleRateHz.ToString() and ToString("N"), but ToString("N") includes thousand separators (e.g., "1,000"), which may not match user input like "1000".

  • ChannelEnabler GuidString: Generated via Guid.NewGuid() on every ChannelList assignment. Not persisted across sessions or used for cross-reference (only in CheckboxesOnListChanged for lookup). Likely a temporary ID with no semantic meaning.

  • ChannelEnabler ChannelId: Set to Convert.ToInt64(ch.Id) (from IGroupChannel.Id), but ch.Id is a string. If ch.Id is non-numeric, Convert.ToInt64 throws FormatException. This is a critical risk. (Note: ParseChannelId is used only for ITestChannel.ChannelId.)

  • ChannelEnabler ChannelId for ITestChannel: Uses ParseChannelId(ch.ChannelId), which may return -1 for invalid IDs. No validation ensures -1 is not a valid channel ID elsewhere.

  • ChannelEnabler ChannelId for IGroupChannel: Uses Convert.ToInt64(ch.Id), which may throw FormatException if ch.Id is non-numeric (e.g., GUID-based IDs). This is inconsistent with ITestChannel handling and a likely source of crashes.

  • ChannelEnabler ChannelId for ROI matching: In CheckboxesOnListChanged, ChannelId is used to build checkedIds, but ROI ChannelIds may contain IDs from ParseChannelId (which may be -1), while IGroupChannel.Id may throw. This mismatch could cause ROI channel assignments to fail silently.

  • ChannelEnabler ChannelId for AllChannelSSNs: Uses ch.Id (from GroupChannel), but AllChannelSSNs does not use ChannelId—it uses Hardware + "\\" + serialNumber. Thus, ROI channel matching relies on ChannelId, but export relies on string-based descriptors. Inconsistency may cause mismatches.

  • ChannelEnabler ChannelId for ParseChannelId: The ParseChannelId method is only used for ITestChannel.ChannelId, but ChannelEnabler.ChannelId is set from IGroupChannel.Id via Convert.ToInt64(ch.Id). This means ROI channel IDs may be inconsistent between test metadata (ParseChannelId) and group channels (Convert.ToInt64).

  • ChannelEnabler ChannelId for ChannelFilter: The `Channel