Added proper links in some readme files

This commit is contained in:
Quinton den Haan 2024-06-01 15:07:12 +02:00
parent 48d8b8f665
commit 72715dc3d8
10 changed files with 102 additions and 36 deletions

View file

@ -31,9 +31,9 @@ git clone https://github/TBD/spectral.git
cd spectral
```
Download [Docker](TODO) and [Docker compose](TODO) following the instructions on the website.
Download [Docker](https://docs.docker.com/desktop/) and [Docker compose](https://docs.docker.com/compose/install/) following the instructions on the website.
(NOTE: For MacOS (especially Apple Sillicon), consider using [colima](TODO) as the runtime)
(NOTE: For MacOS (especially Apple Sillicon), consider using [colima](https://formulae.brew.sh/formula/colima) as the runtime)
Then, run the compose file

View file

@ -35,7 +35,7 @@ Other than that, `pnpm` is mostly used to add new dependencies (either `pnpm add
### Styling
We use [tailwind](TODO) for styling and [shadcn-svelte](https://www.shadcn-svelte.com/docs) for components.
We use [tailwind](https://tailwindcss.com/docs/installation) for styling and [shadcn-svelte](https://www.shadcn-svelte.com/docs) for components.
Try to use tailwind for most styling. Of course, if you want to something more complicated you can use regular `<style>` tags.
@ -100,7 +100,7 @@ export const yourModeData = {
} satisfies ModeValidator;
```
Then you need to fill in each field. Each field is a [zod](TODO) parser, which defines shapes that can be validated at runtime. Visit the [zod docs](TODO) for more info.
Then you need to fill in each field. Each field is a [zod](https://zod.dev/) parser, which defines shapes that can be validated at runtime. Visit the [zod docs](https://zod.dev/?id=introduction) for more info.
- `computedFileData` is the data that is directly computed from the file. This info should be implemented in the Kernel.
- `modeState` is synced state that is stored per-mode (and per-pane). Used for things such as toggles and sliders set by the user. For example, whether to show the legend in the graph.

View file

@ -162,7 +162,11 @@ def validate_frame_index(data, file_state):
status_code=400, detail="startIndex should be strictly lower than endIndex"
)
if start_index < 0:
raise HTTPException(status_code=400, detail="startIndex should be larger or equal to 0")
raise HTTPException(
status_code=400, detail="startIndex should be larger or equal to 0"
)
if end_index > len(data):
raise HTTPException(status_code=400, detail="endIndex should be lower than the file length")
raise HTTPException(
status_code=400, detail="endIndex should be lower than the file length"
)
return {"startIndex": start_index, "endIndex": end_index}

View file

@ -106,7 +106,9 @@ async def frame_fundamental_features(frame: Frame):
"f2": formants[1],
}
except Exception as _:
raise HTTPException(status_code=400, detail="Input data did not meet requirements")
raise HTTPException(
status_code=400, detail="Input data did not meet requirements"
)
@app.post(
@ -151,7 +153,9 @@ async def signal_fundamental_features(signal: Signal):
"formants": formants,
}
except Exception as _:
raise HTTPException(status_code=400, detail="Input data did not meet requirements")
raise HTTPException(
status_code=400, detail="Input data did not meet requirements"
)
@app.get(

View file

@ -43,7 +43,9 @@ def signal_to_sound(signal, fs):
result = signal_to_sound(signal, fs)
```
"""
return parselmouth.Sound(values=np.array(signal).astype("float64"), sampling_frequency=fs)
return parselmouth.Sound(
values=np.array(signal).astype("float64"), sampling_frequency=fs
)
def calculate_signal_duration(signal, fs):
@ -94,7 +96,9 @@ def calculate_sound_pitch(sound, time_step=None):
return None
def calculate_sound_spectrogram(sound, time_step=0.002, window_length=0.005, frequency_step=20.0):
def calculate_sound_spectrogram(
sound, time_step=0.002, window_length=0.005, frequency_step=20.0
):
"""
This method calculates the spectrogram of a sound fragment.
@ -154,7 +158,9 @@ def calculate_sound_f1_f2(sound, time_step=None, window_length=0.025):
```
"""
try:
formants = sound.to_formant_burg(time_step=time_step, window_length=window_length)
formants = sound.to_formant_burg(
time_step=time_step, window_length=window_length
)
data = []
for frame in np.arange(1, len(formants) + 1):
data.append(

View file

@ -201,7 +201,9 @@ def deepgram_transcription(data):
res = []
for word in response["results"]["channels"][0]["alternatives"][0]["words"]:
res.append({"value": word["word"], "start": word["start"], "end": word["end"]})
res.append(
{"value": word["word"], "start": word["start"], "end": word["end"]}
)
return res
except Exception as e:

View file

@ -19,7 +19,9 @@ typical_1_fs, typical_1_data = wv.read(
)
typical_1_data = typical_1_data.tolist()
with open(os.path.join(data_dir, "torgo-dataset/MC02_control_head_sentence1.wav"), mode="rb") as f:
with open(
os.path.join(data_dir, "torgo-dataset/MC02_control_head_sentence1.wav"), mode="rb"
) as f:
control_sentence = f.read()
@ -99,7 +101,9 @@ def test_zero_fs():
def test_fundamental_features_typical_speech():
response = client.post("/signals/analyze", json={"data": typical_1_data, "fs": typical_1_fs})
response = client.post(
"/signals/analyze", json={"data": typical_1_data, "fs": typical_1_fs}
)
assert response.status_code == 200
result = response.json()
assert result["duration"] == pytest.approx(4.565, 0.01)
@ -198,7 +202,9 @@ def test_fundamental_features_empty_signal():
def test_signal_correct_mode_file_not_found(db_mock, file_state):
db_mock.fetch_file.side_effect = HTTPException(status_code=500, detail="database error")
db_mock.fetch_file.side_effect = HTTPException(
status_code=500, detail="database error"
)
response = client.get(
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
)
@ -230,7 +236,9 @@ def test_signal_correct_spectrogram(db_mock, file_state):
def test_signal_correct_waveform(db_mock, file_state):
response = client.get("/signals/modes/waveform", params={"fileState": json.dumps(file_state)})
response = client.get(
"/signals/modes/waveform", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 200
result = response.json()
assert result is None
@ -255,7 +263,9 @@ def test_signal_correct_transcription(db_mock, file_state):
def test_signal_mode_wrong_mode(db_mock, file_state):
response = client.get("/signals/modes/wrongmode", params={"fileState": json.dumps(file_state)})
response = client.get(
"/signals/modes/wrongmode", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 422
assert db_mock.fetch_file.call_count == 0
@ -286,17 +296,23 @@ def test_signal_mode_frame_start_index_bigger_than_end_index(db_mock, file_state
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 400
assert response.json()["detail"] == "startIndex should be strictly lower than endIndex"
assert (
response.json()["detail"] == "startIndex should be strictly lower than endIndex"
)
assert db_mock.fetch_file.call_count == 1
def test_signal_mode_frame_start_index_bigger_than_end_index_equal_numbers(db_mock, file_state):
def test_signal_mode_frame_start_index_bigger_than_end_index_equal_numbers(
db_mock, file_state
):
file_state["frame"] = {"startIndex": 2, "endIndex": 2}
response = client.get(
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 400
assert response.json()["detail"] == "startIndex should be strictly lower than endIndex"
assert (
response.json()["detail"] == "startIndex should be strictly lower than endIndex"
)
assert db_mock.fetch_file.call_count == 1
@ -361,7 +377,9 @@ def test_signal_mode_vowel_space_mode_with_frame(db_mock, file_state):
def test_signal_mode_transcription_db_problem(db_mock):
db_mock.fetch_file.side_effect = HTTPException(status_code=500, detail="database error")
db_mock.fetch_file.side_effect = HTTPException(
status_code=500, detail="database error"
)
response = client.get("/transcription/deepgram/session/1")
assert response.status_code == 404
assert response.json()["detail"] == "File not found"
@ -369,7 +387,9 @@ def test_signal_mode_transcription_db_problem(db_mock):
def test_transcription_model_found(db_mock):
with patch("spectral.transcription.deepgram_transcription") as mock_deepgram_transcription:
with patch(
"spectral.transcription.deepgram_transcription"
) as mock_deepgram_transcription:
mock_deepgram_transcription.return_value = [
{"value": "word1", "start": 0.5, "end": 1.0},
{"value": "word2", "start": 1.5, "end": 2.0},
@ -389,14 +409,19 @@ def test_transcription_storing_error(db_mock):
db_mock.store_transcription.side_effect = HTTPException(
status_code=500, detail="database error"
)
with patch("spectral.transcription.deepgram_transcription") as mock_deepgram_transcription:
with patch(
"spectral.transcription.deepgram_transcription"
) as mock_deepgram_transcription:
mock_deepgram_transcription.return_value = [
{"value": "word1", "start": 0.5, "end": 1.0},
{"value": "word2", "start": 1.5, "end": 2.0},
]
response = client.get("/transcription/deepgram/session/1")
assert response.status_code == 500
assert response.json()["detail"] == "Something went wrong while storing the transcription"
assert (
response.json()["detail"]
== "Something went wrong while storing the transcription"
)
assert db_mock.fetch_file.call_count == 1
assert db_mock.store_transcription.call_count == 1
@ -456,7 +481,9 @@ def mock_db(mocker):
def test_error_rate_no_ground_truth(db_mock, file_state):
db_mock.fetch_file.return_value["groundTruth"] = None
response = client.get("/signals/modes/error-rate", params={"fileState": json.dumps(file_state)})
response = client.get(
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 200
assert response.json() is None
@ -465,7 +492,9 @@ def test_error_rate_no_ground_truth(db_mock, file_state):
def test_error_rate_no_transcription(db_mock, file_state):
response = client.get("/signals/modes/error-rate", params={"fileState": json.dumps(file_state)})
response = client.get(
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 200
@ -517,7 +546,9 @@ def test_error_rate_no_transcription(db_mock, file_state):
def test_error_rate_ground_truth(db_mock, file_state):
file_state["transcriptions"] = [[{"value": "hi"}]]
response = client.get("/signals/modes/error-rate", params={"fileState": json.dumps(file_state)})
response = client.get(
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
)
assert response.status_code == 200
result = response.json()

View file

@ -17,21 +17,28 @@ with open(
def test_speed_voiced_frame():
assert (
calculate_frame_duration(frame_data["voiced-1"]["data"], frame_data["voiced-1"]["fs"])
calculate_frame_duration(
frame_data["voiced-1"]["data"], frame_data["voiced-1"]["fs"]
)
== 0.04
)
def test_speed_unvoiced_frame():
assert (
calculate_frame_duration(frame_data["unvoiced-1"]["data"], frame_data["unvoiced-1"]["fs"])
calculate_frame_duration(
frame_data["unvoiced-1"]["data"], frame_data["unvoiced-1"]["fs"]
)
== 0.04
)
def test_speed_noise_frame():
assert (
calculate_frame_duration(frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"]) == 0.04
calculate_frame_duration(
frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"]
)
== 0.04
)
@ -47,13 +54,17 @@ def test_pitch_voiced():
def test_pitch_unvoiced():
assert math.isnan(
calculate_frame_pitch(frame_data["unvoiced-1"]["data"], frame_data["unvoiced-1"]["fs"])
calculate_frame_pitch(
frame_data["unvoiced-1"]["data"], frame_data["unvoiced-1"]["fs"]
)
)
def test_pitch_noise():
assert math.isnan(
calculate_frame_pitch(frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"])
calculate_frame_pitch(
frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"]
)
)
@ -62,7 +73,9 @@ def test_pitch_empty_frame():
def test_formants_voiced_frame():
formants = calculate_frame_f1_f2(frame_data["voiced-1"]["data"], frame_data["voiced-1"]["fs"])
formants = calculate_frame_f1_f2(
frame_data["voiced-1"]["data"], frame_data["voiced-1"]["fs"]
)
assert len(formants) == 2
assert formants[0] == pytest.approx(474.43, 0.01)
assert formants[1] == pytest.approx(1924.64, 0.01)
@ -78,7 +91,9 @@ def test_formants_unvoiced_frame():
def test_formants_noise_frame():
formants = calculate_frame_f1_f2(frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"])
formants = calculate_frame_f1_f2(
frame_data["noise-1"]["data"], frame_data["noise-1"]["fs"]
)
assert len(formants) == 2
assert formants[0] == pytest.approx(192.72, 0.01)
assert formants[1] == pytest.approx(1864.27, 0.01)

View file

@ -47,7 +47,9 @@ def test_deepgram_transcription(mock_deepgram_client):
]
}
}
mock_client_instance.listen.prerecorded.v("1").transcribe_file.return_value = mock_response
mock_client_instance.listen.prerecorded.v(
"1"
).transcribe_file.return_value = mock_response
data = b"audio data"
result = deepgram_transcription(data)

View file

@ -5,7 +5,9 @@ from spectral.frame_analysis import validate_frame_index
def test_validate_frame_index_valid():
data = [0] * 100
frame_index = validate_frame_index(data, {"frame": {"startIndex": 10, "endIndex": 20}})
frame_index = validate_frame_index(
data, {"frame": {"startIndex": 10, "endIndex": 20}}
)
assert frame_index == {"startIndex": 10, "endIndex": 20}