Repository: nilearn/nilearn

Issue:
Possible inappropriate usage of scipy.stats.gamma.pdf in _gamma_difference_hrf
<!--Provide a brief description of the bug.-->
Here (`scipy.stats.gamma.pdf(x, a, loc=0, scale=1)`) is the gamma pdf function statement. And following is how it's used in nilearn. `delay / dispersion` and `dt / dispersion` strangely correspond to `a` and `loc` respectively, while `scale` with default value `1`.

https://github.com/nilearn/nilearn/blob/cc11c85cd222632dcaf7c51005b549f75a49bbc4/nilearn/glm/first_level/hemodynamic_models.py#L18-L74

See more details here #2981. With this usage, we cannot produce the right Glover HRF even if we add the normalization constant.

<!--Please fill in the following information, to the best of your ability.-->
Nilearn version: 0.9.1

### Steps and code to reproduce bug

Here is the comparison between different versions of Glover HRF.

```python
import numpy as np
from scipy.stats import gamma
from nilearn.glm.first_level import glover_hrf
import matplotlib.pyplot as plt

# Glover 1999
def original_glover(t, n1=6.0, t1=0.9, a2=0.35, n2=12.0, t2=0.9):
    """As seen in Glover 1999, page 420. Using values for the auditory cortex (canonical Glover).
    Variables are named after those in the paper.
    """
    gamma1 = t**n1 * np.exp(-t / t1)
    gamma2 = t**n2 * np.exp(-t / t2)
    c1 = gamma1.max()
    c2 = gamma2.max()
    return gamma1 / c1 - a2 * gamma2 / c2


# glover_hrf with normalized gamma
def _gamma_difference_hrf(tr, oversampling=50, time_length=32., onset=0.,
                          delay=6, undershoot=16., dispersion=1.,
                          u_dispersion=1., ratio=0.167):
    """
    Minimum modification from nilearn.glm.first_level._gamma_difference_hrf
    (only add the normalization constant)
    """
    dt = tr / oversampling
    time_stamps = np.linspace(0, time_length,
                              np.rint(float(time_length) / dt).astype(int))
    time_stamps -= onset
    
    peak = gamma.pdf(time_stamps, delay / dispersion, dt / dispersion)
    undershoot = gamma.pdf(time_stamps, undershoot / u_dispersion, dt / u_dispersion)
    hrf = peak / peak.max() - ratio * undershoot / undershoot.max()
    hrf /= hrf.sum()
    return hrf


def glover_hrf_with_norm(tr, oversampling=50, time_length=32., onset=0.):
    """
    Same as nilearn.glm.first_level.glover_hrf
    """
    return _gamma_difference_hrf(tr, oversampling, time_length, onset,
                                 delay=6, undershoot=12., dispersion=.9,
                                 u_dispersion=.9, ratio=.35)


# code from dangom
# https://github.com/nilearn/nilearn/issues/2981#issuecomment-1160515684
def sample(t: np.array):
    # Glover parameters
    peak_delay: float = 6.0
    peak_width: float = 0.9
    undershoot_delay: float = 12.0
    undershoot_width: float = 0.9
    positive_negative_ratio: float = 0.35

    peak = gamma.pdf(t, peak_delay / peak_width, loc=1/50, scale=peak_width)
    undershoot = gamma.pdf(
        t,
        undershoot_delay / undershoot_width,
        loc=0,
        scale=undershoot_width,
    )
    peak_norm = peak.max()
    undershoot_norm = undershoot.max()

    hrf = peak / peak_norm - undershoot / undershoot_norm * positive_negative_ratio
    return hrf


t = np.arange(0, 32, 0.02)  # 32 seconds, with a TR=1 and oversampling=50 (hence 0.02).
# Plot both HRFs normalized so we can compare their shape.
plt.plot(t, original_glover(t) / original_glover(t).max(), label="Glover 1999")
plt.plot(t, glover_hrf(1) / glover_hrf(1).max(), label="nilearn glover_hrf")
plt.plot(t, glover_hrf_with_norm(1) / glover_hrf_with_norm(1).max(), label="glover_hrf with normalized gamma")
plt.plot(t, sample(t) / sample(t).max(), label="code from dangom")
plt.grid()
plt.legend()
plt.show()
```

Here is the result
![image](https://user-images.githubusercontent.com/19154386/176695171-b11cf44f-128d-4d6f-b52c-6ea6fa262a35.png)



Discussion:
I think that your definition is both more accurate and simpler.
Please open a PR !

Resolving patch:
diff --git a/nilearn/glm/first_level/hemodynamic_models.py b/nilearn/glm/first_level/hemodynamic_models.py
--- a/nilearn/glm/first_level/hemodynamic_models.py
+++ b/nilearn/glm/first_level/hemodynamic_models.py
@@ -65,11 +65,21 @@ def _gamma_difference_hrf(tr, oversampling=50, time_length=32., onset=0.,
     time_stamps = np.linspace(0, time_length,
                               np.rint(float(time_length) / dt).astype(int))
     time_stamps -= onset
-    hrf = (
-        gamma.pdf(time_stamps, delay / dispersion, dt / dispersion)
-        - ratio * gamma.pdf(time_stamps,
-                            undershoot / u_dispersion, dt / u_dispersion)
-    )
+
+    # define peak and undershoot gamma functions
+    peak_gamma = gamma.pdf(
+        time_stamps,
+        delay / dispersion,
+        loc=dt,
+        scale=dispersion)
+    undershoot_gamma = gamma.pdf(
+        time_stamps,
+        undershoot / u_dispersion,
+        loc=dt,
+        scale=u_dispersion)
+
+    # calculate the hrf
+    hrf = peak_gamma - ratio * undershoot_gamma
     hrf /= hrf.sum()
     return hrf
 


Test patch: