diff --git a/HRM_Processor.py b/HRM_Processor.py
index b3ed186..c4bdeda 100644
--- a/HRM_Processor.py
+++ b/HRM_Processor.py
@@ -34,6 +34,10 @@ class HRM_Processor:
                                                                    "time"])
         self.output_dict["duration"] = ecg_strip_duration
 
+        beat_start_times = self.determine_beat_start_times(
+            self.input_data["time"], self.input_data["voltage"])
+        self.output_dict["start_times"] = beat_start_times
+
     def determine_voltage_extremes(self, voltage):
         """Determines the min and max values of the voltage data
 
@@ -71,3 +75,153 @@ class HRM_Processor:
         """
         strip_duration = np.amax(time)
         return strip_duration
+
+    def determine_beat_start_times(self, time, voltage):
+        """
+
+        Parameters
+        ----------
+        time:       Numpy array
+                    Time values read in from CSV file
+        voltage:    Numpy array
+                    Voltages read in from CSV file
+
+        Returns
+        -------
+        start_times:    Numpy array
+                        Start times of each beat (defined as the time of the
+                        peak of each QRS complex)
+        """
+        threshold = self.determine_threshold(voltage)
+        inx_above_threshold = self.find_indices_above_threshold(voltage,
+                                                                threshold)
+        beat_sep_points = self.find_beat_separation_points(inx_above_threshold)
+        qrs_peak_inx = self.find_beat_separation_points(beat_sep_points)
+        start_times = self.index_beat_start_times(time, qrs_peak_inx)
+        return start_times
+
+    def determine_threshold(self, voltage):
+        """Determines the threshold
+
+        Parameters
+        ----------
+        voltage:    Numpy array
+                    The voltage values from the CSV file
+
+        Returns
+        -------
+        threshold:  float
+                    The 'threshold' for the QRS complex, which occurs when
+                    the voltage value is above 75% of its original value.
+        """
+        threshold = 0.75*np.amax(voltage)
+
+        return threshold
+
+    def find_indices_above_threshold(self, voltage, threshold):
+        """Finds the indices of the voltage array where it is above the
+        threshold value.
+
+        Parameters
+        ----------
+        voltage:    Numpy array
+                    The voltage values from the CSV file
+        threshold:  float
+                    The threshold for the QRS complex, determined by the
+                    determine_threshold function.
+
+        Returns
+        -------
+        indices:    Numpy array
+                    The indices for which the voltage data exceeds the
+                    threshold value.
+        """
+        indices = np.argwhere(voltage > threshold).flatten()
+        return indices
+
+    def find_beat_separation_points(self, indices_array):
+        """Find the separation points between beats, i.e. the points where
+        the difference between neighboring elements in the indices_array > 2
+        (which indicates areas where there was 'jump' in indices above the
+        threshold). Then, it indexes those indices in the indices_array,
+        to determine the indices in the original voltage arrays where the
+        'end' of a beat occurs.
+
+        Parameters
+        ----------
+        indices_array:  numpy array
+                        The indices of the voltage array where the voltage
+                        values are above the threshold values.
+
+        Returns
+        -------
+        beat_sep_inx:   numpy array
+                        The indices in the voltage array where there is a
+                        discontinuity b/w voltage indices above the
+                        threshold. So, these represent the points at which
+                        the QRS complex goes below the threshold for each
+                        beat (i.e. the beat separations)
+        """
+
+        diff_array = np.diff(indices_array)
+        beat_seps = np.argwhere(diff_array > 2).flatten()
+        beat_sep_inx = indices_array[beat_seps]
+        return beat_sep_inx
+
+    def find_qrs_peak_indices(self, voltage, beat_sep_inx):
+        """Indexes the voltage array during the QRS complex of each beat (
+        determined by the indices specified in beat_sep_inx), finds the max
+        value in each range, and finds the index of the peak of the QRS
+        complex.
+
+        Parameters
+        ----------
+        voltage:    numpy array
+                    Voltage values read in from CSV file
+        beat_sep_inx:   Numpy array
+                        Indices where the voltage goes below the threshold
+                        value at the end of the QRS complex
+
+        Returns
+        -------
+        qrs_peak_locations  numpy array
+                            The locations of the peak of the QRS complex for
+                            each beat
+        """
+        start_inx = 0
+        qrs_peak_locations = np.array([])
+        for beat_sep in beat_sep_inx:
+            temp = voltage[start_inx:beat_sep+1]
+            qrs_max_loc = start_inx + np.asscalar(np.where(temp == np.amax(
+                temp))[0])
+            qrs_peak_locations = np.append(qrs_peak_locations, qrs_max_loc)
+            start_inx = beat_sep
+        temp = voltage[beat_sep+1:np.alen(voltage)]
+        qrs_max_loc = start_inx + np.asscalar(np.where(temp == np.amax(
+            temp))[0])
+        qrs_peak_locations = np.append(qrs_peak_locations, qrs_max_loc)
+
+        return qrs_peak_locations
+
+    def index_beat_start_times(self, time, qrs_peak_locations):
+        """Determines the beat start times by indexing the time array at the
+        peak of each QRS complex, as determined by qrs_locations.
+
+        Parameters
+        ----------
+        time:               numpy array
+                            Time array read in from the CSV file.
+
+        qrs_peak_locations: numpy array
+                            Contains the indices where the peak of the QRS
+                            complex for each beat is located.
+
+        Returns
+        -------
+        beat_start_times    numpy array
+                            Contains the times where each beat starts (defined
+                            as the time of the peak of the QRS complex)
+        """
+
+        beat_start_times = time[qrs_peak_locations].flatten()
+        return beat_start_times

The HRM_Processor needs to determine the times at which the beats occurred.
The HRM_Processor needs to be able to determine the times at which the beats occurred. For the initial implementation of this functionality, this will be based on a thresholding operation - a beat will be defined by the time of the maximum value when the voltage has exceeded a certain threshold. This feature will consist of multiple functions, roughly as follows:

- [x] - Calculate threshold = 75% of the maximum value (roughly, the precise % is subject to change)
- [x] - Find the indices associated with the voltage array exceeding that threshold value.
- [x] - Segment the indices into continuous sections (i.e. find ranges where the indices are consecutive or almost consecutive, followed by an instance where there is a large separation between indices). The point at which there is a large separation between indices represents the beginning of a new segment
- [x] - Find the index of the maximum voltage values for each segment.
- [x] - Index the time array at the point of the maximum voltage values, which is the "start time" of a beat.
diff --git a/test_HRM_Processor.py b/test_HRM_Processor.py
index ef1ec7e..951fb0f 100644
--- a/test_HRM_Processor.py
+++ b/test_HRM_Processor.py
@@ -18,10 +18,23 @@ def test_HRM_Processor_init(dr):
 
     """
     hrm_proc = HRM_Processor(dr)
+    print(hrm_proc.input_data)
     assert dr.output_dict == hrm_proc.input_data
 
 
 def test_voltage_extremes(hrm):
+    """Tests the determine_voltage_extremes function to ensure that it
+    returns the max and minimum of a voltage numpy array as a tuple in the
+    form (max, min)
+
+    Parameters
+    ----------
+    hrm:        A basic HRM_Processor object
+
+    Returns
+    -------
+    None
+    """
 
     voltage = np.array([-1.502, -7.9, 3.5, 2.1, 8.7, 4.0])
 
@@ -31,11 +44,35 @@ def test_voltage_extremes(hrm):
 
 
 def test_write_outputs_to_dict_voltage_extremes(hrm):
+    """Tests that the voltage extremes was written to the output_dict of the
+    HRM_Processor object from the voltage data. The comparision is to the
+    'voltage' extremes of test_file.csv
+
+    Parameters
+    ----------
+    hrm:    Basic HRM_Processor object made from test_file.csv
+
+    Returns
+    -------
+    None
+    """
 
     assert hrm.output_dict["voltage_extremes"] == (10.0, 20.0)
 
 
 def test_determine_ecg_strip_duration(hrm):
+    """Checks that the determine_ecg_strip duration function the max value
+    of the time numpy array that it is given.
+
+    Parameters
+    ----------
+    hrm: HRM_Processor
+         Basic HRM_Processor object made from test_file.csv
+
+    Returns
+    -------
+    None
+    """
     time = np.array([0, 2.2, 5, 7.5])
 
     strip_duration = hrm.determine_ecg_strip_duration(time)
@@ -44,5 +81,125 @@ def test_determine_ecg_strip_duration(hrm):
 
 
 def test_write_strip_duration(hrm):
+    """Tests that the max value of the time numpy array was written to the
+    "duration" entry of the output_dict upon construction of hrm. The max
+    time value in test_file.csv is 2.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            Basic HRM_Processor object created from test_file.csv
+
+    Returns
+    -------
+    None
+    """
 
     assert hrm.output_dict["duration"] == 2
+
+
+def test_determine_threshold(hrm):
+    """Checks that the determine_threshold function returns 90% of the
+    highest value in a numpy array
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            Basic HRM_Processor object created from test_file.csv
+    Returns
+    -------
+    None
+    """
+
+    voltage = np.array([1, 2, 4, 10, 5])
+    threshold = hrm.determine_threshold(voltage)
+    assert threshold == 7.5
+
+
+def test_find_indices_above_threshold(hrm):
+    """Tests that the find_indices_above_threshold function finds the
+    indices where the voltage numpy array are above a specified threshold.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            Basic HRM_Processor object created from test_file.csv
+
+    Returns
+    -------
+    None
+    """
+
+    threshold = 5
+    voltage = np.array([1, 6, 4, 5, 2, 9.7])
+
+    expected_indices = np.array([1, 5])
+    calculated_indices = hrm.find_indices_above_threshold(voltage, threshold)
+
+    assert np.array_equal(calculated_indices, expected_indices)
+
+
+def test_find_beat_separation_points(hrm):
+    """Tests the find_beat_separation_points function, which finds indices
+    in an array of indices where there is a separation between consecutive
+    indices greater than 2 (indicating the likely start of a new beat/QRS
+    complex).
+
+    Parameters
+    ----------
+    hrm:        HRM_Processor
+                Generic HRM_Processor created from test_file.csv
+
+    Returns
+    -------
+
+    """
+    indices = np.array([1, 2, 3, 4, 5, 10, 11, 12, 14, 30, 31, 32, 40, 41])
+    expected_sep_inx = np.array([5, 14, 32])
+    measured_sep_inx = hrm.find_beat_separation_points(indices)
+
+    assert np.array_equal(measured_sep_inx, expected_sep_inx)
+
+
+def test_find_qrs_peak_indices(hrm):
+    """Tests the find_qrs_peak_indices function, which should be able to
+    find the maximum value in a voltage array within a range specified by
+    the beat_sep_inx array.
+
+    Parameters
+    ----------
+    hrm:        HRM_Processor
+                A basic HRM_Processor object made from input_file.csv
+
+    Returns
+    -------
+    None
+    """
+    beat_sep_inx = np.array([3, 5, 10])
+    voltage = np.array([0, 1, 6, 8, 12, 10, 18, 4, 29, 1, 8, 3, 5, 6])
+    expected_beat_max = np.array([3, 4, 8, 12])
+    measured_beat_max = hrm.find_qrs_peak_indices(voltage, beat_sep_inx)
+
+    assert np.array_equal(expected_beat_max, measured_beat_max)
+
+
+def test_index_beat_start_times(hrm):
+    """Tests index_beat_start_times, which finds the start times of a beat
+    in a numpy array based on indices specified by the qrs_peak_locations
+    array.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            A basic HRM_Processor created from test_file.csv
+
+    Returns
+    -------
+    None
+    """
+    qrs_peak_locations = np.array([0, 3, 6, 8])
+    time = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    expected_start_times = np.array([1, 4, 7, 9])
+    measured_start_times = hrm.index_beat_start_times(time, qrs_peak_locations)
+
+    assert np.array_equal(expected_start_times, measured_start_times)

pip install -r requirements.txt
parse_log_pytest
requirements.txt
pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
exceptiongroup==1.2.2
execnet==2.1.1
iniconfig==2.1.0
numpy==2.0.2
packaging==24.2
pep8==1.7.1
pluggy==1.5.0
pytest==8.3.5
pytest-cache==1.0
pytest-cov==6.0.0
pytest-pep8==1.0.6
tomli==2.2.1

name: bme590hrm
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - iniconfig==2.1.0
      - numpy==2.0.2
      - packaging==24.2
      - pep8==1.7.1
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-cache==1.0
      - pytest-cov==6.0.0
      - pytest-pep8==1.0.6
      - tomli==2.2.1
prefix: /opt/conda/envs/bme590hrm

test_HRM_Processor.py::test_determine_threshold test_HRM_Processor.py::test_find_indices_above_threshold test_HRM_Processor.py::test_find_beat_separation_points test_HRM_Processor.py::test_index_beat_start_times
test_HRM_Processor.py::test_find_qrs_peak_indices
test_HRM_Processor.py::test_HRM_Processor_init test_HRM_Processor.py::test_voltage_extremes test_HRM_Processor.py::test_write_outputs_to_dict_voltage_extremes test_HRM_Processor.py::test_determine_ecg_strip_duration test_HRM_Processor.py::test_write_strip_duration
HRM_Processor.py
HRM_Processor.py