STFT amplitude normalization, librosa library
I did answer a similar question a few years back, but can't find it. Basically, you are losing the energy because of the windowing. It's true to say that you should multiply the spectra by 2 in order to retrieve the energy from negative frequencies and divide it by the window size. However, this is only true for the rectangular window. The correct normalization should be following:

out = 2 * np.abs(out) / np.sum(window)

where np.sum(window) in case of rectangular (or no window in fact), is equal to the frame size. It kind of makes sense, right? If window attenuates the signal at its ends, then this energy should be recovered somehow.

Now back to your problem, librosa.stft by default uses hann window which looks like:



If you calculate the np.sum(np.hanning(window_size)) you will get $511.5$. If you read up more, the coherent gain of that window is approximately $0.5$. Which is defined as:

$$CG = \frac{\Sigma w(N)}{N} $$

So in order to solve your problem, I suggest to explicitly pass the window vector to the stft function:

import librosa
import numpy as np
import matplotlib.pyplot as plt

frequency = 1000
fs = 4 * frequency
time_vector = 1/fs * np.arange(20000)

sine_wave = np.sin(2 * np.pi * frequency * time_vector)

window_size = 1024
window = np.hanning(window_size)
hop_length = 512

out  = librosa.core.spectrum.stft(sine_wave, n_fft = window_size, hop_length = hop_length, window=window)
out = 2 * np.abs(out) / np.sum(window)

fig = plt.figure()
axes = fig.add_subplot(111)
im = axes.pcolormesh(out)
fig.colorbar(im)

plt.figure()
slice = out[:, 10]
plt.plot(slice)
plt.grid()

plt.show()


Which will return: