Write once, run everywhere - Create interactive GUIs that work seamlessly in both Jupyter notebooks and terminal environments with the exact same code.
EZInput is a Python library that simplifies creating user interfaces for data science, image analysis, and computational research. Whether you're prototyping in a notebook or deploying a command-line tool, your UI code remains identical.
- 🔄 Unified API: Same code works in Jupyter notebooks (.ipynb) and terminal scripts (.py)
- 📋 Copy-Pastable: Move code between notebooks and scripts without modification
- 💾 Auto-Persistence: Widget values are automatically saved and restored between sessions
- 🎯 Type-Safe: Validated inputs with type checking (int, float, text, paths, etc.)
- 🎨 Rich Widgets: Sliders, dropdowns, text inputs, file pickers, and more
- ⚡ Zero Boilerplate: No environment detection code needed - it just works
Note: This project is currently in alpha stage and may undergo API changes.
- Image analysis pipelines requiring user parameters
- Data science workflows with interactive configuration
- Research tools that need both notebook and CLI interfaces
- Teaching and tutorials with reproducible parameter sets
Install EZInput using pip:
pip install ezinputFor development with all optional dependencies:
pip install ezinput[all]- Python >= 3.9
- Dependencies are automatically installed:
ipywidgetsandipyfilechooserfor Jupyter notebooksprompt-toolkitfor terminal interfacespyyamlfor configuration persistence
from ezinput import EZInput
# Create a GUI (works in both Jupyter and terminal!)
gui = EZInput("my_app")
# Add widgets - order of adding determines display order
name = gui.add_text("name", "Enter your name:")
age = gui.add_int_range("age", "Select age:", 18, 100)
confirm = gui.add_check("confirm", "Proceed with analysis?")
# In Jupyter: display the widgets
gui.show()
# Access values
print(f"Hello {name.value}, you are {age.value} years old")
if confirm.value:
print("Starting analysis...")Execute notebooks as scripts using the ezinput command:
# Run a notebook from terminal
ezinput my_notebook.ipynb
# The notebook will execute with terminal-style prompts
# for all EZInput widgetsThis is perfect for:
- Running analysis pipelines in batch mode
- Deploying notebook-based tools as CLI applications
- CI/CD integration
💡 Copy-Paste Friendly: This exact code works in both:
- Jupyter notebooks (.ipynb files) - displays interactive widgets
- Terminal scripts (.py files) - shows interactive prompts
Run in terminal with:
python my_script.pyHere's a complete example for an image analysis pipeline:
from ezinput import EZInput
import numpy as np
# One EZInput instance per analysis task recommended
gui = EZInput("image_analysis_v1")
# Configuration section
gui.add_label(value="=== Image Processing Parameters ===")
input_file = gui.add_text("input", "Input image path:",
placeholder="/path/to/image.tif")
threshold = gui.add_float_range("threshold", "Detection threshold:",
0.0, 1.0)
min_size = gui.add_int_range("min_size", "Minimum object size (pixels):",
10, 1000)
gui.add_label(value="=== Output Options ===")
save_results = gui.add_check("save", "Save results to disk?")
output_format = gui.add_dropdown("format", ["PNG", "TIFF", "JPEG"],
"Output format:")
# Display GUI (in Jupyter) or collect inputs (in terminal)
gui.show()
# Your analysis code using the values
def run_analysis(values):
"""Process image with user-provided parameters."""
print(f"Processing {values['input'].value}")
print(f"Using threshold: {values['threshold'].value}")
print(f"Minimum size: {values['min_size'].value}")
# Your image processing code here
# ...
if values['save'].value:
print(f"Saving as {values['format'].value}")
# Add a callback button (in Jupyter: creates button, in terminal: executes after show())
gui.add_callback("process", run_analysis, gui.get_values(),
description="Run Analysis")Key Points:
- Widget tags (like
"input","threshold") must be unique within each GUI instance - Create one
EZInputinstance per analysis task or computational pipeline - The order you add widgets is the order they appear on screen
- Use
remember_value=Trueparameter on widgets to persist values between runs
In Jupyter Notebook:
- Widgets appear as interactive UI elements
- Sliders, dropdowns, and buttons are fully visual
- Call
gui.show()to display the interface
In Terminal:
- Interactive prompts appear sequentially
- Type-validated input with autocomplete
- Press Enter to submit each value
- Run with:
python your_script.py
EZInput automatically detects whether it's running in Jupyter or terminal:
from ezinput import EZInput
# No environment detection code needed!
gui = EZInput("my_app")
# Or import specific versions if needed:
from ezinput import EZInputJupyter # Jupyter-specific
from ezinput import EZInputPrompt # Terminal-specificValues are automatically saved to ~/.ezinput/{title}.yml. Priority order:
- Loaded parameters (from
load_parameters()) - Remembered values (from previous runs, if
remember_value=Trueparameter) - Explicit defaults (passed via
default=parameter) - Widget defaults (empty string, 0, False, etc.)
# Values automatically persist when remember_value=True (default for most widgets)
gui = EZInput("my_app")
age = gui.add_int_range("age", "Age:", 0, 120, remember_value=True)
# Next time: shows the previously entered value!
# Load from specific configuration file
gui = EZInput("my_app")
gui.load_parameters("experiment_config.yml")
# Or save current values to share
gui.save_parameters("my_optimal_params.yml")Create shareable configuration files:
gui = EZInput("my_analysis")
# ... add widgets and collect values ...
# Save current values
gui.save_parameters("my_config.yml") # Explicit filename
# or
gui.save_parameters("") # Auto-named: my_analysis_parameters.yml
# Share this file with colleagues!
# They can load it:
gui2 = EZInput("my_analysis", params_file="my_config.yml")Config file location:
- Explicit saves: wherever you specify
- Auto-saved settings:
~/.ezinput/{title}.yml
Resetting to defaults:
# Clear all remembered values - returns widgets to original defaults
gui.restore_defaults()
# ⚠️ Important: Re-run your cell (Jupyter) or script (terminal) to see the reset take effect
# The method removes the memory file but doesn't immediately change displayed widgetsAll widgets work identically in Jupyter and terminal. Here's the complete reference:
| Widget | Description | Example |
|---|---|---|
add_text() |
Single-line text input | gui.add_text("name", "Your name:") |
add_text_area() |
Multi-line text input | gui.add_text_area("notes", "Comments:") |
# Text input with placeholder
email = gui.add_text("email", "Email address:",
placeholder="user@example.com")
# Multi-line text area
notes = gui.add_text_area("description", "Project description:",
placeholder="Enter detailed description...")| Widget | Description | Example |
|---|---|---|
add_int_range() |
Integer slider/input with bounds | gui.add_int_range("count", "Count:", 1, 100) |
add_float_range() |
Float slider/input with bounds | gui.add_float_range("alpha", "Alpha:", 0.0, 1.0) |
add_int_text() |
Integer input (no bounds) | gui.add_int_text("age", "Age:") |
add_float_text() |
Float input (no bounds) | gui.add_float_text("weight", "Weight:") |
add_bounded_int_text() |
Integer input with validation | gui.add_bounded_int_text("percent", "%:", 0, 100) |
add_bounded_float_text() |
Float input with validation | gui.add_bounded_float_text("ratio", "Ratio:", 0.0, 1.0) |
# Slider-style (Jupyter) or validated prompt (terminal)
iterations = gui.add_int_range("iter", "Iterations:", 10, 1000)
# Free-form number input
temperature = gui.add_float_text("temp", "Temperature (°C):")
# Bounded input with automatic clamping
percentage = gui.add_bounded_int_text("coverage", "Coverage %:", 0, 100)| Widget | Description | Example |
|---|---|---|
add_check() |
Boolean yes/no or checkbox | gui.add_check("verbose", "Enable verbose output?") |
add_dropdown() |
Single selection from list | gui.add_dropdown("method", ["A", "B", "C"], "Method:") |
# Boolean checkbox (Jupyter) or yes/no prompt (terminal)
debug = gui.add_check("debug", "Enable debug mode?")
# Dropdown with autocomplete in terminal
algorithm = gui.add_dropdown("algo",
["linear", "rbf", "polynomial"],
"Interpolation method:")| Widget | Environment | Description |
|---|---|---|
add_path_completer() |
Terminal only | Path input with autocomplete |
add_file_upload() |
Jupyter only | Visual file picker |
# Terminal: autocomplete-enabled path input
input_file = gui.add_path_completer("input", "Select input file:")
# Jupyter: visual file browser
input_file = gui.add_file_upload("input", accept="*.tif")| Widget | Description | Example |
|---|---|---|
add_label() |
Static text/header | gui.add_label(value="=== Settings ===") |
add_output() |
Output display area (Jupyter) | gui.add_output("results") |
add_HTML() |
Rich HTML content (Jupyter only) | gui.add_HTML("info", "<b>Note:</b> ...") |
# Section headers
gui.add_label(value="=== Input Parameters ===")
# Output area for results (Jupyter)
gui.add_output("results")
with gui["results"]:
print("Analysis complete!")
# Rich formatting (Jupyter only)
gui.add_HTML("warning", '<p style="color:red">⚠️ Experimental feature</p>')# add_callback ensures naming consistency between Jupyter and terminal
def process_data(values):
"""Process data with current parameter values."""
threshold = values["threshold"].value
method = values["method"].value
print(f"Processing with {method} at threshold {threshold}")
# Your processing code here...
# In Jupyter: creates a button
# In terminal: executes immediately
gui.add_callback("run", process_data, gui.get_values(),
description="Start Processing")Why add_callback?
The name maintains API consistency between Jupyter (button-based) and terminal (immediate execution) interfaces.
# ✅ Good: unique tags
gui.add_text("input_file", "File:")
gui.add_text("output_file", "Output:")
# ❌ Bad: duplicate tags
gui.add_text("file", "File:")
gui.add_text("file", "Output:") # Overwrites the first one!# ✅ Good: separate GUIs for different analyses
preprocessing_gui = EZInput("preprocessing_v1")
analysis_gui = EZInput("main_analysis_v2")
# ❌ Avoid: reusing the same GUI instance for different purposes
gui = EZInput("multi_purpose") # Config files may conflict# ✅ Good: descriptive, versioned titles
gui = EZInput("cell_segmentation_v2")
gui = EZInput("image_denoising_2024")
# ✅ Also good: user/project specific
gui = EZInput("john_experiment_setup")
# ❌ Avoid: generic titles that may conflict
gui = EZInput("test")
gui = EZInput("gui")# Widgets appear in the order you add them
gui = EZInput("my_app")
# This order...
gui.add_label(value="=== Step 1 ===")
gui.add_text("name", "Name:")
gui.add_int_range("age", "Age:", 0, 120)
gui.add_label(value="=== Step 2 ===")
gui.add_check("confirm", "Confirm?")
# ...is the order users see them in
gui.show()- Full Documentation: Comprehensive guides and API reference
- Widget Gallery: Interactive examples of all widgets
- Tutorials: Step-by-step guides
- API Reference: Detailed method documentation
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE.txt file for details.
Made with ❤️ for the scientific Python community from ezinput.ezinput_jupyter import EZInputJupyter
gui = EZInputJupyter() gui.add_text('name', description='Name:', style={'description_width': 'initial'})
## Example: Adding a Custom Widget
```python
# Pass a widget class (with args/kwargs)
gui.add_custom_widget('slider', widgets.IntSlider, min=0, max=10, value=5)
# Or pass an already-instantiated widget
dropdown = widgets.Dropdown(options=['A', 'B', 'C'], value='A')
gui.add_custom_widget('dropdown', dropdown)
from ezinput import EZInput
gui = EZInput(title="Terminal Example")
gui.add_text("username", "Enter your username:", remember_value=True)
gui.add_int_range("age", "Enter your age:", 18, 100, remember_value=True)
gui.save_settings()from ezinput import EZInput
gui = EZInput(title="Jupyter Example")
gui.add_text("username", description="Enter your username:", remember_value=True)
gui.add_int_range("age", description="Enter your age:", vmin=18, vmax=100, remember_value=True)
gui.show()This project is licensed under the MIT License. See the LICENSE file for details.
