diff --git a/pulse_lib/acquisition/measurement_converter.py b/pulse_lib/acquisition/measurement_converter.py
index a146b6ec89e9330b8da06002c3241e0f6bdfe741..76351f6921f484dbcb9cf1a6fb2dc6ca5db9ab87 100644
--- a/pulse_lib/acquisition/measurement_converter.py
+++ b/pulse_lib/acquisition/measurement_converter.py
@@ -170,6 +170,52 @@ class MeasurementParameter(MultiParameter):
         self.units += (unit,)
         self.labels += (label,)
 
+    def add_sensor_histogram(self, sensor, bins, range, accepted_only=False):
+        '''
+        Adds histograms for all measurements of sensor.
+
+        Args:
+            sensor (str): name of sensor in pulse-lib.
+            bins (int): number of bins in histogram.
+            range (Tuple[float,float]): upper and lower edge of histogram.
+            accepted_only (bool): if True only count accepted shots
+        '''
+        for m in self._mc._description.measurements:
+            if getattr(m, 'acquisition_channel', None) == sensor:
+                self.add_measurement_histogram(m.name, bins, range,
+                                               accepted_only=accepted_only)
+
+    def add_measurement_histogram(self, m_name, bins, range, accepted_only=False):
+        '''
+        Adds histogram for specified measurement.
+
+        Args:
+            m_name (str): name of measurement in sequence.
+            bins (int): number of bins in histogram.
+            range (Tuple[float,float]): upper and lower edge of histogram.
+            accepted_only (bool): if True only count accepted shots
+        '''
+        def _histogram(data):
+            if accepted_only:
+                if 'mask' not in data:
+                    raise Exception('Cannot filter on accepted. Accept mask is not in data.')
+                d = data[m_name][data['mask'].astype(bool)]
+            else:
+                d = data[m_name]
+            return np.histogram(d, bins=binedges)[0]/d.shape[0]
+
+        binedges  = np.linspace(range[0], range[1], bins+1)
+        bincenters = (binedges[1:] + binedges[:-1])/2
+        setpoints = (tuple(bincenters),)
+        setpoint_names = ('sensor_val',)
+        self.add_derived_param(
+                f'{m_name}_hist',
+                _histogram,
+                unit='',
+                setpoints=setpoints,
+                setpoint_units=('mV',),
+                setpoint_names=setpoint_names,
+                setpoint_labels=setpoint_names)
 
     def get_raw(self):
         data = self._source.get_channel_data()
diff --git a/pulse_lib/tests/acquire/test_measurement_histogram.py b/pulse_lib/tests/acquire/test_measurement_histogram.py
new file mode 100644
index 0000000000000000000000000000000000000000..108097226eec85006b7660f3efbbeb602746a7ce
--- /dev/null
+++ b/pulse_lib/tests/acquire/test_measurement_histogram.py
@@ -0,0 +1,28 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+
+
+#%%
+def test1():
+    pulse = context.init_pulselib(n_gates=2, n_sensors=2)
+
+    s = pulse.mk_segment()
+
+    s.P1.add_block(0, 20, 100)
+    s.P2.add_block(0, 20, -100)
+    s.SD1.acquire(0, 1000, wait=True)
+    s.SD2.acquire(0, 1000, wait=True, threshold=3000, accept_if=1)
+
+    sequence = pulse.mk_sequence([s])
+    sequence.n_rep = 10
+    m_param = sequence.get_measurement_param(selectors=False, total_selected=False)
+    m_param.add_sensor_histogram('SD1', 20, (0.0, 20_000.0))
+    m_param.add_sensor_histogram('SD2', 20, (0.0, 20_000.0), accepted_only=True)
+    context.add_hw_schedule(sequence)
+
+    return context.run('histogram', sequence, m_param)
+
+#%%
+
+if __name__ == '__main__':
+    ds1 = test1()