A Windows application for visualizing and comparing CSV data. Built with PyQt6 and PyQtGraph for high-performance chart rendering. Built for the purpose of comparing OBDII data, but can be used for anything.
- CSV File Loading: Open single or multiple CSV files via File menu or drag-and-drop
- Multi-Channel CSV Support: Handles interleaved multi-channel data (Car Scanner format) - automatically separates channels
- Past Imports: Home screen shows recently opened files for quick access
- Double-click to reopen
- Ctrl+Click for multi-select, then "Open Selected" to load multiple files at once
- Clear History button to reset
- Add Import: Load additional CSV files to compare against the current data
- Individual Channel Plots: Each channel displayed on its own graph with synchronized X-axis
- Multi-Import Overlay: Same channels from different files plotted on the same graph with different colors
- Click-to-Position Crosshair: Click anywhere on a chart to see exact values from all imports at that time
- Channel Title Values: Each chart title shows the current value at the crosshair position
- Adjustable Graph Heights: Taller/Shorter buttons adjust all chart heights by 5% increments
- LOD Optimization: Level-of-detail downsampling (max 2000 points) for smooth performance with large datasets
- Scroll Wheel: Scrolls the graph area vertically
- Ctrl+Scroll: Zooms X-axis in/out centered on last clicked position (or view center if none)
Located in the sidebar:
- Start/End Time Inputs: Directly set the visible time range
- Center Time + Go: Jump to a specific time
- Navigation Buttons: ±0.1s, ±0.5s, ±1s, ±5s, ±15s, ±30s, ±1min, ±5min
- Zoom Slider: Drag left to zoom out (full range), right to zoom in (minimum 10 seconds)
- Uses exponential scaling for natural zoom feel
- Zooms centered on last clicked position (if within view), otherwise view center
- Reset View: Red button in center returns to full data range
In the sidebar below time navigation:
- Chart Checkbox: Single checkbox to show/hide the entire chart (determines Shown vs Hidden section)
- Import Dot Buttons: Colored circles next to each channel - click to toggle individual import lines
- Solid circle = import line visible
- Hollow ring = import line hidden
- Show All / Hide All: Quick toggle buttons for all channels
- Dynamic Sorting: Shown channels appear at top, sorted by unit then alphabetically
In the "Imports" section of the sidebar:
- Color Legend: Each import has a distinct color (click the color dot to change it)
- Duration Display: Shows total time span of each import
- Time Offset: Shows offset relative to base import (first loaded)
- Sync Button: Opens synchronization dialog for non-base imports
When comparing multiple CSV files:
- Base Import: First loaded file is the reference (offset = 0)
- Synchronize Dialog: Click "Sync" button next to any additional import
- Adjust time offset with ±0.1s to ±5min buttons
- Or type offset directly
- Charts update in real-time as you adjust
Create calculated channels from existing data:
- Create Math Channel Button: Opens the math channel dialog
- Up to 5 Inputs: Select channels A through E from dropdowns
- Expression Field: Python-style math expressions like
(A/0.45) * 14.7orA + B * C - Real-time Validation: Expression must evaluate to a number
- Unit Selection: Autocomplete from existing units
- Edit Button: Modify existing math channels
Available Functions:
- Basic:
abs,min,max,sqrt,log,log10,exp,pow - Trig:
sin,cos,tan - Rounding:
floor,ceil,round - Statistical:
rolling_avg(X, seconds),rolling_min,rolling_max,delta,cumsum - Clipping:
clip(X, min, max) - Array-wide:
np_min,np_max,np_mean,np_std - Conditionals:
if_else(condition, true_val, false_val) - Constants:
pi,e - Comparisons:
<,>,<=,>=,==,!=
Show or hide data based on conditions:
- Create Filter Button: Opens filter dialog
- Filter Name: Required identifier
- Boolean Expression: Must evaluate to True/False (e.g.,
A > 100) - Show/Hide Mode:
- Show (👁): Display only matching data
- Hide (🚫): Hide matching data
- Time Buffer: ±0.1s to ±10min around each match point
- Filter Precedence: Top filter = highest precedence (use up/down buttons to reorder)
- Cross-Import Sync: If any import matches, all imports show/hide that time range (respects offsets)
- Enable/Disable: Checkbox to toggle each filter without deleting
- Resizable Sidebar: Drag the splitter between sidebar and charts
- Split Window Mode: View menu → Detach sidebar to separate window (useful for dual monitors)
- Persistence: Window size, position, and splitter ratio saved between sessions
Save and restore complete analysis sessions:
- Save View: File → Save View (Ctrl+S) to save current state with a name
- Pre-fills name if re-saving an existing view
- Home Screen: Saved views appear on the right side of the home screen
- Double-click or select and click "Open Selected" to load
- Delete button (🗑) next to each view for quick removal
- "Delete All" button to clear all saved views
- Auto-prompt: When closing or opening new files, you'll be prompted to save (skipped if no changes)
What's saved in a view:
- CSV file paths (with relocation dialog if files move)
- Import colors and time offsets
- Math channels and filters
- Chart visibility (shown/hidden) and import line visibility per channel
- Time range and zoom level
- Graph heights (taller/shorter setting)
- Split window mode state
Storage location: Documents/OBD2Analyzer/views/
The tool expects semicolon-delimited CSV files with these columns:
| Column | Required | Description |
|---|---|---|
| SECONDS | Yes | Timestamp in seconds |
| VALUE | Yes | Sensor reading |
| UNITS | Yes | Unit of measurement (e.g., "rpm", "°F") |
| PID | Yes | Channel/sensor name |
Example (Car Scanner format):
SECONDS;VALUE;UNITS;PID
0.0;800;rpm;Engine RPM
0.0;25;°C;Coolant Temp
0.1;850;rpm;Engine RPM
0.1;25;°C;Coolant TempThe parser handles interleaved rows - different channels can have different sample rates.
- Ask Saurabh for obd2_analyzer.zip
- Extract the zip
- Run
obd2_analyzer.exe
-
Create and activate a virtual environment:
python -m venv venv venv\Scripts\activate # Windows # source venv/bin/activate # Linux/Mac
-
Install dependencies:
pip install -r requirements.txt
-
Run the application:
python src/obd2_native.py
Run the build script from the repo root:
run/build_exe.batThis will:
- Install PyInstaller if needed
- Convert
logo.pngtorun/logo.ico - Build the executable using
run/obd2_analyzer.spec
Output will be in run/dist/obd2_analyzer.exe
src/
├── obd2_native.py # Application entry point
└── obd2_viewer/
├── core/ # Core data processing
│ ├── data_loader.py # CSV file loading
│ └── multi_channel_parser.py # Multi-channel CSV parsing
├── dialogs/ # Dialog windows
│ ├── loading_dialog.py # Loading spinner
│ ├── synchronize_dialog.py # Time sync dialog
│ ├── math_channel_dialog.py # Math channel creation
│ ├── filter_dialog.py # Filter creation
│ └── expression_helpers.py # Shared expression evaluation
├── widgets.py # Reusable UI components
├── data_types.py # Data structures (ImportData, etc.)
├── main_window.py # Main application window
└── chart_widget.py # PyQtGraph chart components# Run all tests
pytest src/test/ -v- Data Format: Only supports semicolon-delimited CSV with SECONDS;PID;VALUE;UNITS columns
- Export: No chart export functionality yet
- Sessions: No way to save/restore visualization sessions
- Chart export (PNG, SVG)
- Data annotations and markers
- Session save/restore
- Additional CSV format support
Issue: DLL load failed while importing QtWidgets when running the PyInstaller-built exe
Root Cause: PyQt6 6.10.1 has DLL loading compatibility issues with PyInstaller on Windows. Additionally, shiboken2 (PySide2's Qt5 binding) was being bundled and conflicting with PyQt6.
Solution:
- Downgraded PyQt6 to 6.5.2
- Added exclusions for PySide2, shiboken2, PySide6, shiboken6, PyQt5 in spec file
- Pinned PyQt6 version in requirements.txt to
>=6.5.0,<6.6.0
Issue: "No files could be loaded from this view" error and app hanging when loading a saved view
Root Cause: _load_file() uses a background thread and returns immediately. The view manager was checking mw.imports and trying to apply colors/offsets before files finished loading, resulting in empty imports list.
Solution:
- Refactored
load_saved_viewto queue files using main window's existing queue system - Added
_on_view_files_loadedcallback that runs after all files finish loading - Updated
_load_next_queued_fileto call the callback when queue empties - Added null checks for
_loading_dialogsince saved view loading doesn't create one
Issue: Dash callbacks not triggering graph display (legacy web version)
Root Cause: Dashboard was creating its own Dash app instance, but callbacks were registered on a different instance.
Solution: Modified dashboard to accept external Dash app for callback registration.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with PyQt6
- Charts powered by PyQtGraph
- Data processing with Pandas and NumPy
- Data format from Car Scanner