Added proper links in some readme files
This commit is contained in:
parent
48d8b8f665
commit
72715dc3d8
10 changed files with 102 additions and 36 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue