Repository: Qiskit/qiskit

Issue:
Schedule Drawer should have an option to ignore channels with just framechange
Certain channels in a schedule only have framechanges, there should be an option to not plot those channels. 

<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?





Discussion:
@AzizNgoueya, would you be interested in taking this issue on? Your work looked great on scaling and we could use the help 😄.
@taalexander sure i will examine this issue when i will have more time.
Great, thank you very much! I've assigned it to you. We really appreciate your contributions.
Hello Aziz, have you had a chance to take a look at this issue?
Hello @taalexander, i haven't examined this issue yet(i was on vacation), i will look into it this week.

Resolving patch:
diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py
--- a/qiskit/pulse/schedule.py
+++ b/qiskit/pulse/schedule.py
@@ -363,7 +363,8 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None,
              scaling: float = None, channels_to_plot: Optional[List[Channel]] = None,
              plot_all: bool = False, plot_range: Optional[Tuple[float]] = None,
              interactive: bool = False, table: bool = True, label: bool = False,
-             framechange: bool = True, channels: Optional[List[Channel]] = None):
+             framechange: bool = True, channels: Optional[List[Channel]] = None,
+             show_framechange_channels: bool = True):
         """Plot the schedule.
 
         Args:
@@ -381,6 +382,7 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None,
             label: Label individual instructions
             framechange: Add framechange indicators
             channels: A list of channel names to plot
+            show_framechange_channels: Plot channels with only framechanges
 
         Returns:
             matplotlib.figure: A matplotlib figure object of the pulse schedule.
@@ -399,7 +401,8 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None,
                                           scaling=scaling, plot_all=plot_all,
                                           plot_range=plot_range, interactive=interactive,
                                           table=table, label=label,
-                                          framechange=framechange, channels=channels)
+                                          framechange=framechange, channels=channels,
+                                          show_framechange_channels=show_framechange_channels)
 
     def __eq__(self, other: ScheduleComponent) -> bool:
         """Test if two ScheduleComponents are equal.
diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py
--- a/qiskit/visualization/pulse/matplotlib.py
+++ b/qiskit/visualization/pulse/matplotlib.py
@@ -34,6 +34,7 @@
                                    SnapshotChannel)
 from qiskit.pulse import (SamplePulse, FrameChange, PersistentValue, Snapshot,
                           Acquire, PulseError)
+from qiskit.pulse.commands.frame_change import FrameChangeInstruction
 
 
 class EventsOutputChannels:
@@ -281,17 +282,23 @@ def __init__(self, style):
         """
         self.style = style or SchedStyle()
 
-    def _build_channels(self, schedule, channels, t0, tf):
+    def _build_channels(self, schedule, channels, t0, tf, show_framechange_channels=True):
         # prepare waveform channels
         drive_channels = collections.OrderedDict()
         measure_channels = collections.OrderedDict()
         control_channels = collections.OrderedDict()
         acquire_channels = collections.OrderedDict()
         snapshot_channels = collections.OrderedDict()
+        _channels = set()
+        if show_framechange_channels:
+            _channels.update(schedule.channels)
+        # take channels that do not only contain framechanges
+        else:
+            for start_time, instruction in schedule.instructions:
+                if not isinstance(instruction, FrameChangeInstruction):
+                    _channels.update(instruction.channels)
 
-        _channels = list(schedule.channels) + channels
-        _channels = list(set(_channels))
-
+        _channels.update(channels)
         for chan in _channels:
             if isinstance(chan, DriveChannel):
                 try:
@@ -550,7 +557,7 @@ def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, v_max,
     def draw(self, schedule, dt, interp_method, plot_range,
              scaling=None, channels_to_plot=None, plot_all=True,
              table=True, label=False, framechange=True,
-             channels=None):
+             channels=None, show_framechange_channels=True):
         """Draw figure.
 
         Args:
@@ -566,6 +573,7 @@ def draw(self, schedule, dt, interp_method, plot_range,
             label (bool): Label individual instructions
             framechange (bool): Add framechange indicators
             channels (list[OutputChannel]): channels to draw
+            show_framechange_channels (bool): Plot channels with only framechanges
 
         Returns:
             matplotlib.figure: A matplotlib figure object for the pulse schedule
@@ -600,7 +608,8 @@ def draw(self, schedule, dt, interp_method, plot_range,
 
         # prepare waveform channels
         (schedule_channels, output_channels,
-         snapshot_channels) = self._build_channels(schedule, channels, t0, tf)
+         snapshot_channels) = self._build_channels(schedule, channels, t0, tf,
+                                                   show_framechange_channels)
 
         # count numbers of valid waveform
         n_valid_waveform, v_max = self._count_valid_waveforms(output_channels, scaling=scaling,
diff --git a/qiskit/visualization/pulse_visualization.py b/qiskit/visualization/pulse_visualization.py
--- a/qiskit/visualization/pulse_visualization.py
+++ b/qiskit/visualization/pulse_visualization.py
@@ -31,7 +31,7 @@ def pulse_drawer(data, dt=1, style=None, filename=None,
                  interp_method=None, scaling=None, channels_to_plot=None,
                  plot_all=False, plot_range=None, interactive=False,
                  table=True, label=False, framechange=True,
-                 channels=None):
+                 channels=None, show_framechange_channels=True):
     """Plot the interpolated envelope of pulse
 
     Args:
@@ -52,6 +52,7 @@ def pulse_drawer(data, dt=1, style=None, filename=None,
         label (bool): Label individual instructions
         framechange (bool): Add framechange indicators
         channels (list): A list of channel names to plot
+        show_framechange_channels (bool): Plot channels with only framechanges
 
     Returns:
         matplotlib.figure: A matplotlib figure object for the pulse envelope
@@ -74,7 +75,8 @@ def pulse_drawer(data, dt=1, style=None, filename=None,
         drawer = _matplotlib.ScheduleDrawer(style=style)
         image = drawer.draw(data, dt=dt, interp_method=interp_method, scaling=scaling,
                             plot_range=plot_range, plot_all=plot_all, table=table,
-                            label=label, framechange=framechange, channels=channels)
+                            label=label, framechange=framechange, channels=channels,
+                            show_framechange_channels=show_framechange_channels)
     else:
         raise VisualizationError('This data cannot be visualized.')
 


Test patch:
diff --git a/test/python/visualization/references/schedule_show_framechange_ref.png b/test/python/visualization/references/schedule_show_framechange_ref.png
new file mode 100644
Binary files /dev/null and b/test/python/visualization/references/schedule_show_framechange_ref.png differ
diff --git a/test/python/visualization/test_pulse_visualization_output.py b/test/python/visualization/test_pulse_visualization_output.py
--- a/test/python/visualization/test_pulse_visualization_output.py
+++ b/test/python/visualization/test_pulse_visualization_output.py
@@ -35,6 +35,7 @@ class TestPulseVisualizationImplementation(QiskitVisualizationTestCase):
     pulse_matplotlib_reference = path_to_diagram_reference('pulse_matplotlib_ref.png')
     instr_matplotlib_reference = path_to_diagram_reference('instruction_matplotlib_ref.png')
     schedule_matplotlib_reference = path_to_diagram_reference('schedule_matplotlib_ref.png')
+    schedule_show_framechange_ref = path_to_diagram_reference('schedule_show_framechange_ref.png')
 
     def setUp(self):
         self.schedule = Schedule()
@@ -120,6 +121,22 @@ def test_truncate_acquisition(self):
         # Check ValueError is not thrown
         sched.draw(plot_range=(0, 15))
 
+    # TODO: Enable for refactoring purposes and enable by default when we can
+    # decide if the backend is available or not.
+    @unittest.skipIf(not HAS_MATPLOTLIB, 'matplotlib not available.')
+    @unittest.skip('Useful for refactoring purposes, skipping by default.')
+    def test_schedule_drawer_show_framechange(self):
+        filename = self._get_resource_path('current_show_framechange_ref.png')
+        gp0 = pulse_lib.gaussian(duration=20, amp=1.0, sigma=1.0)
+        sched = Schedule()
+        sched = sched.append(gp0(DriveChannel(0)))
+        sched = sched.insert(60, FrameChange(phase=-1.57)(DriveChannel(0)))
+        sched = sched.insert(30, FrameChange(phase=-1.50)(DriveChannel(1)))
+        sched = sched.insert(70, FrameChange(phase=1.50)(DriveChannel(1)))
+        pulse_drawer(sched, filename=filename, show_framechange_channels=False)
+        self.assertImagesAreEqual(filename, self.schedule_show_framechange_ref)
+        os.remove(filename)
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)