Commit
•
408b86f
1
Parent(s):
0a2902e
feat: Add sample livebooks
Browse filesBrought in from Hugging Face space at https://huggingface.co/spaces/headway/medical-code-transcriber/tree/main/public-apps
livebooks/sample_implementation.livemd
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!-- livebook:{"app_settings":{"auto_shutdown_ms":5000,"multi_session":true,"slug":"medical-code-transcriber"}} -->
|
2 |
+
|
3 |
+
# Medical Code Transcriber
|
4 |
+
|
5 |
+
```elixir
|
6 |
+
Mix.install(
|
7 |
+
[
|
8 |
+
{:kino_bumblebee, "~> 0.4.0"},
|
9 |
+
{:exla, ">= 0.0.0"},
|
10 |
+
{:explorer, "~> 0.7.0"},
|
11 |
+
{:kino_explorer, "~> 0.1.11"}
|
12 |
+
],
|
13 |
+
config: [nx: [default_backend: EXLA.Backend]]
|
14 |
+
)
|
15 |
+
```
|
16 |
+
|
17 |
+
## Transcribe Audio to Text
|
18 |
+
|
19 |
+
### Step 1: Select your audio to transcribe
|
20 |
+
|
21 |
+
* First, upload (or record) your audio below.
|
22 |
+
* Then, run the second cell after the input to transcribe the audio to text.
|
23 |
+
|
24 |
+
```elixir
|
25 |
+
{:ok, model_info} = Bumblebee.load_model({:hf, "openai/whisper-tiny"})
|
26 |
+
{:ok, featurizer} = Bumblebee.load_featurizer({:hf, "openai/whisper-tiny"})
|
27 |
+
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "openai/whisper-tiny"})
|
28 |
+
{:ok, generation_config} = Bumblebee.load_generation_config({:hf, "openai/whisper-tiny"})
|
29 |
+
generation_config = Bumblebee.configure(generation_config, max_new_tokens: 100)
|
30 |
+
|
31 |
+
serving =
|
32 |
+
Bumblebee.Audio.speech_to_text_whisper(
|
33 |
+
model_info,
|
34 |
+
featurizer,
|
35 |
+
tokenizer,
|
36 |
+
generation_config,
|
37 |
+
compile: [batch_size: 4],
|
38 |
+
chunk_num_seconds: 30,
|
39 |
+
timestamps: :segments,
|
40 |
+
stream: true,
|
41 |
+
defn_options: [compiler: EXLA]
|
42 |
+
)
|
43 |
+
|
44 |
+
audio_input = Kino.Input.audio("Audio", sampling_rate: featurizer.sampling_rate)
|
45 |
+
```
|
46 |
+
|
47 |
+
```elixir
|
48 |
+
chosen_audio = Kino.Input.read(audio_input)
|
49 |
+
|
50 |
+
audio =
|
51 |
+
chosen_audio.file_ref
|
52 |
+
|> Kino.Input.file_path()
|
53 |
+
|> File.read!()
|
54 |
+
|> Nx.from_binary(:f32)
|
55 |
+
|> Nx.reshape({:auto, chosen_audio.num_channels})
|
56 |
+
|> Nx.mean(axes: [1])
|
57 |
+
|
58 |
+
dataframe =
|
59 |
+
Nx.Serving.run(serving, audio)
|
60 |
+
|> Enum.reduce([], fn chunk, acc ->
|
61 |
+
[start_mark, end_mark] =
|
62 |
+
for seconds <- [chunk.start_timestamp_seconds, chunk.end_timestamp_seconds] do
|
63 |
+
seconds |> round() |> Time.from_seconds_after_midnight() |> Time.to_string()
|
64 |
+
end
|
65 |
+
|
66 |
+
[%{start_mark: start_mark, end_mark: end_mark, text: chunk.text}] ++ acc
|
67 |
+
end)
|
68 |
+
|> Enum.reverse()
|
69 |
+
|> Explorer.DataFrame.new()
|
70 |
+
```
|
71 |
+
|
72 |
+
```elixir
|
73 |
+
procedure_code_mapping = [
|
74 |
+
["followup visit", "FOLLOWUP"],
|
75 |
+
["cipher drug", "CIPHER"],
|
76 |
+
["catheterization", "CATH"],
|
77 |
+
["ventricularography", "VTR"],
|
78 |
+
["ejection fraction", "FR"]
|
79 |
+
]
|
80 |
+
|
81 |
+
codes_series =
|
82 |
+
dataframe
|
83 |
+
|> Explorer.DataFrame.pull("text")
|
84 |
+
|> Explorer.Series.downcase()
|
85 |
+
|> Explorer.Series.transform(fn element ->
|
86 |
+
Enum.flat_map(procedure_code_mapping, fn [term, code] ->
|
87 |
+
case String.contains?(element, term) do
|
88 |
+
true -> [code]
|
89 |
+
false -> []
|
90 |
+
end
|
91 |
+
end)
|
92 |
+
end)
|
93 |
+
|
94 |
+
dataframe
|
95 |
+
|> Explorer.DataFrame.put("codes", codes_series)
|
96 |
+
```
|
livebooks/using_audio_tagger_library.livemd
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!-- livebook:{"app_settings":{"auto_shutdown_ms":5000,"multi_session":true,"show_source":true,"slug":"transcriber"}} -->
|
2 |
+
|
3 |
+
# Tag Audio
|
4 |
+
|
5 |
+
```elixir
|
6 |
+
Mix.install(
|
7 |
+
[
|
8 |
+
{:audio_tagger, path: "./development/ml/audio_tagger"},
|
9 |
+
{:kino_bumblebee, "~> 0.4.0"},
|
10 |
+
{:exla, ">= 0.0.0"},
|
11 |
+
{:explorer, "~> 0.7.0"},
|
12 |
+
{:kino_explorer, "~> 0.1.11"}
|
13 |
+
],
|
14 |
+
config: [
|
15 |
+
nx: [default_backend: EXLA.Backend]
|
16 |
+
# exla: [
|
17 |
+
# clients: [
|
18 |
+
# cuda: [
|
19 |
+
# platform: :cuda,
|
20 |
+
# lazy_transfers: :never
|
21 |
+
# ]
|
22 |
+
# ]
|
23 |
+
# ]
|
24 |
+
]
|
25 |
+
)
|
26 |
+
```
|
27 |
+
|
28 |
+
## Step 1: Create Vector Embeddings for ICD-9 Codes
|
29 |
+
|
30 |
+
```elixir
|
31 |
+
# Use sentence-transformers/all-MiniLM-L6-v2 to create vectors for each medical code description
|
32 |
+
tmpfile = Path.join(System.tmp_dir(), "CMS32_DESC_LONG_SHORT_DX")
|
33 |
+
|
34 |
+
AudioTagger.Vectors.precalculate(tmpfile)
|
35 |
+
```
|
36 |
+
|
37 |
+
## Step 2: Transcribe Audio Recording
|
38 |
+
|
39 |
+
```elixir
|
40 |
+
# 1 - Prepare model and choose audio file
|
41 |
+
featurizer = AudioTagger.Transcriber.prepare_featurizer()
|
42 |
+
|
43 |
+
audio_input = Kino.Input.audio("Audio", sampling_rate: featurizer.sampling_rate)
|
44 |
+
```
|
45 |
+
|
46 |
+
```elixir
|
47 |
+
# 2 - Transcribe audio recording to text using OpenAI's Whisper model (takes approximately a minute on an M1 Max)
|
48 |
+
chosen_audio = Kino.Input.read(audio_input)
|
49 |
+
|
50 |
+
if chosen_audio == nil do
|
51 |
+
raise "No file chosen. Please select a file in the widget above."
|
52 |
+
end
|
53 |
+
|
54 |
+
file = chosen_audio.file_ref |> Kino.Input.file_path() |> File.read!()
|
55 |
+
options = [model_name: "openai/whisper-tiny", num_channels: chosen_audio.num_channels]
|
56 |
+
|
57 |
+
transcription_df =
|
58 |
+
AudioTagger.Transcriber.transcribe_audio(featurizer, file, options)
|
59 |
+
|> Enum.map(&Function.identity/1)
|
60 |
+
|> Explorer.DataFrame.new()
|
61 |
+
```
|
62 |
+
|
63 |
+
## Step 3: Tag Transcribed Audio
|
64 |
+
|
65 |
+
```elixir
|
66 |
+
labels_df =
|
67 |
+
"#{tmpfile}.csv"
|
68 |
+
|> Explorer.DataFrame.from_csv!(
|
69 |
+
dtypes: [
|
70 |
+
{"DIAGNOSIS CODE", :string},
|
71 |
+
{"LONG DESCRIPTION", :string},
|
72 |
+
{"SHORT DESCRIPTION", :string}
|
73 |
+
]
|
74 |
+
)
|
75 |
+
|> Explorer.DataFrame.select([0, 1, 2])
|
76 |
+
|> Explorer.DataFrame.rename(["code", "long_description", "short_description"])
|
77 |
+
|
78 |
+
tagged_audio =
|
79 |
+
transcription_df
|
80 |
+
|> AudioTagger.Classifier.SemanticSearch.tag(
|
81 |
+
labels_df,
|
82 |
+
"#{tmpfile}.bin"
|
83 |
+
)
|
84 |
+
```
|