Noah Settersten commited on
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 |
+
```
|