summary refs log tree commit diff
diff options
context:
space:
mode:
authorArun Isaac2016-12-21 02:21:54 +0530
committerArun Isaac2016-12-21 02:21:54 +0530
commitb3484b533476aa6d6263775b7a79670bc9eec70e (patch)
treeb23b559f5aef54bcde689ee7307da762181dce9a
downloadksh-reports-b3484b533476aa6d6263775b7a79670bc9eec70e.tar.gz
ksh-reports-b3484b533476aa6d6263775b7a79670bc9eec70e.tar.lz
ksh-reports-b3484b533476aa6d6263775b7a79670bc9eec70e.zip
Initial commit
-rw-r--r--biometry.asy38
-rw-r--r--ksh-biometry.el41
-rw-r--r--ksh-discharge.el63
-rw-r--r--ksh-emacs-init.el14
-rw-r--r--ksh-forms-improved.el95
-rw-r--r--ksh-report-header.el20
-rw-r--r--ksh-scan2.el177
-rw-r--r--ksh-scan3.el130
-rwxr-xr-xscan-images.sh19
9 files changed, 597 insertions, 0 deletions
diff --git a/biometry.asy b/biometry.asy
new file mode 100644
index 0000000..294c212
--- /dev/null
+++ b/biometry.asy
@@ -0,0 +1,38 @@
+pair pair_ratio(pair a, pair b, real fraction) {
+     return a + (b - a)*fraction;
+}
+real real_ratio(real a, real b, real fraction) {
+     return a + (b - a)*fraction;
+}
+void draw_tic(pair a, pair b, real fraction, real tic_height, bool label) {
+     pair pt1 = pair_ratio(a, b, fraction);
+     pair pt2 = pt1 - (0, tic_height);
+     draw(pt1 -- pt2);
+     if (label) {
+     	label(format("%f\%", fraction*100), pt2, S);
+     }
+}
+void draw_marker(pair a, pair b, real value, real marker_height) {
+     pair markpt = pair_ratio(a, b, value);
+     pair pt = (markpt.x, markpt.y + marker_height);
+     label("*", pt);
+}
+void draw_scale(pair a, pair b, real value, real tic_height) {
+     draw(pair_ratio(a, b, 0) -- pair_ratio(a, b, 1));
+     draw_tic(a, b, 0.05, tic_height, true);
+     draw_tic(a, b, 0.5, tic_height, true);
+     draw_tic(a, b, 0.95, tic_height, true);
+     draw_tic(a, b, 0.25, 0.5*tic_height, false);
+     draw_tic(a, b, 0.75, 0.5*tic_height, false);
+     draw_marker(a, b, value, 0.5*tic_height);
+}
+void draw_biometry(string param, string param_age, real percentile) {
+     real width = 100;
+     real height = 75;
+
+     label(param, (width/2, 2*height/3), N);
+     label(param_age, (width/2, 2*height/3), S);
+     pair scale1 = (0.1*width, 0.25*height);
+     pair scale2 = (0.9*width, 0.25*height);
+     draw_scale(scale1, scale2, percentile/100, 0.1*height);
+}
\ No newline at end of file
diff --git a/ksh-biometry.el b/ksh-biometry.el
new file mode 100644
index 0000000..0e5f1d7
--- /dev/null
+++ b/ksh-biometry.el
@@ -0,0 +1,41 @@
+(defun draw-biometry (param-name param param-age percentile)
+  (let ((temp-script-file (make-temp-script
+			   (format "%s %s" param-name param)
+			   param-age (string-to-number percentile))))
+    (message "Drawing %s..." temp-script-file)
+    (shell-command
+     (format "asy -f pdf -o %s %s" (file-name-directory temp-script-file) temp-script-file))
+    (delete-file temp-script-file)
+    (format "%s.pdf" temp-script-file)))
+
+(defun make-temp-script (param param-age percentile)
+  (let ((temp-script-file (make-temp-file "biometry")))
+    (with-temp-file temp-script-file
+      (insert "include \"biometry\";\n")
+      (insert (format
+	       "draw_biometry(\"%s\", \"%s\", %d);"
+	       param param-age percentile)))
+    temp-script-file))
+
+(defun insert-biometry (readings)
+  (princ
+   (format "[[%s]]\n"
+	   (crop-pdf
+	    (combine-pdfs
+	     (mapcar (lambda (reading)
+			  (apply 'draw-biometry
+				 (mapcar 'get-field reading))) readings))))))
+
+(defun combine-pdfs (pdfs)
+  (let ((temp-combination (make-temp-file "pdfjam" nil ".pdf")))
+    (apply 'call-process "pdfjam" nil nil nil
+	   "--nup" "4x1"
+	   "--outfile" temp-combination pdfs)
+    (mapc 'delete-file pdfs)
+    temp-combination))
+
+(defun crop-pdf (pdf)
+  (message "Cropping %s..." pdf)
+  (let ((output-pdf (format "%s-crop.pdf" (file-name-sans-extension pdf))))
+    (shell-command (format "pdfcrop %s %s" pdf output-pdf))
+    (delete-file pdf) output-pdf))
diff --git a/ksh-discharge.el b/ksh-discharge.el
new file mode 100644
index 0000000..9bd0e6d
--- /dev/null
+++ b/ksh-discharge.el
@@ -0,0 +1,63 @@
+;; -*- lexical-binding: t -*-
+
+(load "ksh-forms-improved.el")
+
+;; Datafile path and fields
+(setq forms-file (expand-file-name "discharge.dat" ksh-data-path))
+(setq forms-number-of-fields
+      (forms-enumerate
+       '(ip-op-no patient-name age sex address admission-date
+		  discharge-date diagnosis surgical-procedure
+		  history clinical-examination course-in-hospital
+		  investigation treatment condition-on-discharge advice)))
+
+;; Default values for fields
+(setq default-field-values
+      `((,sex . "F")
+	(,advice . "Triple A Cal Forte - 30 Cap")))
+
+;; Format specification for form display
+(setq forms-format-list
+      (append
+       (list "Discharge Summary\n\n")
+       (form-entries
+	'(("IP/OP No" . ip-op-no)
+	  ("Age" . age)
+	  ("Sex" . sex)
+	  ("Name of Patient" . patient-name)
+	  ("Address" . address)
+	  ("Date of Admission" . admission-date)
+	  ("Date of Discharge" . discharge-date)
+	  ("Diagnosis" . diagnosis)
+	  ("Surgical Procedure" . surgical-procedure)
+	  ("History" . history)
+	  ("Clinical Examination" . clinical-examination)
+	  ("Course in Hospital" . course-in-hospital)
+	  ("Investigation" . investigation)
+	  ("Treatment" . treatment)
+	  ("Condition on Discharge" . condition-on-discharge)
+	  ("Advice" . advice)))))
+
+(setq form-to-org
+      (lambda ()
+	(with-output-to-string
+	  (make-report-header "DISCHARGE SUMMARY")
+	  (single-line-org-entries
+	   `(("IP/OP No" . ,ip-op-no)
+	     ("Age" . ,age)
+	     ("Sex" . ,sex)
+	     ("Name of Patient" . ,patient-name)
+	     ("Date of Admission" . ,admission-date)
+	     ("Date of Discharge" . ,discharge-date)))
+	  (multi-line-org-entries
+	   `(("Address" . ,address)
+	     ("Diagnosis" . ,diagnosis)
+	     ("Surgical Procedure" . ,surgical-procedure)
+	     ("History" . ,history)
+	     ("Clinical Examination" . ,clinical-examination)
+	     ("Course in Hospital" . ,course-in-hospital)
+	     ("Investigation" . ,investigation)
+	     ("Treatment" . ,treatment)
+	     ("Condition on Discharge" . ,condition-on-discharge)
+	     ("Advice" . ,advice)
+	     ("Signature of the Medical Officer" . ""))))))
diff --git a/ksh-emacs-init.el b/ksh-emacs-init.el
new file mode 100644
index 0000000..1bd14ab
--- /dev/null
+++ b/ksh-emacs-init.el
@@ -0,0 +1,14 @@
+;; Path to data files
+(setq ksh-data-path (expand-file-name "data" ksh-path))
+
+;; Printing settings
+(setq printer-name "HP_P2055DN_CPL")
+(setq lpr-switches
+      (list "-o" "sides=two-sided-long-edge"
+	    "-o" "fit-to-page"))
+
+;; Find forms
+(forms-find-file (expand-file-name "ksh-discharge.el" ksh-path))
+;; (forms-find-file (expand-file-name "ksh-scan1.el" ksh-path))
+(forms-find-file (expand-file-name "ksh-scan2.el" ksh-path))
+(forms-find-file (expand-file-name "ksh-scan3.el" ksh-path))
diff --git a/ksh-forms-improved.el b/ksh-forms-improved.el
new file mode 100644
index 0000000..a8a2e12
--- /dev/null
+++ b/ksh-forms-improved.el
@@ -0,0 +1,95 @@
+;; -*- lexical-binding: t -*-
+
+(require 'org)
+(require 'subr-x)
+(require 'cl-lib)
+
+;; General settings
+(setq doc-view-continuous t)
+(setq lpr-switches (list "-o sides=two-sided-long-edge"
+			 "-o fit-to-page"))
+(setq export-properties
+      (list :section-numbers nil
+	    :with-toc nil))
+
+;; Printing
+(defun print-report ()
+  (interactive)
+  (shell-command
+   (format "lpr -o sides=two-sided-long-edge -o fit-to-page %s" (buffer-file-name))))
+(defalias 'print-buffer 'print-report)
+
+;; Utilities
+(defmacro setfun (function-name function)
+  "Set functions like using define in scheme"
+  `(defun ,function-name (&rest args)
+     (apply ,function args)))
+
+(defun mapped (mapper function)
+  "Apply the mapping function MAPPER on function FUNCTION"
+  (lambda (arglist)
+    (funcall mapper function arglist)))
+
+(defun set-values (record values)
+  "Set record RECORD to values in alist VALUES"
+  (mapc (lambda (field-value)
+	  (aset record (car field-value) (cdr field-value))) values))
+
+;; Entry creation functions for form display
+(defun form-entry (label-field)
+  (list (format "%s\n" (car label-field))
+	(cdr label-field) "\n\n"))
+
+(setfun form-entries (mapped 'cl-mapcan 'form-entry))
+
+(defun new-record-filter (record)
+  (set-values record default-field-values)
+  record)
+(setq forms-new-record-filter 'new-record-filter)
+
+;; Entry creation functions for org export
+(defun make-org-entry-function (format-string)
+  (lambda (label-field)
+    (let* ((label (car label-field))
+	   (field-no (cdr label-field))
+	   (value (get-field field-no)))
+      (if value
+	  (princ (format format-string label
+			 (replace-regexp-in-string "\n" "\n\n" value)))
+	(user-error "Field \"%s\" should not be blank" label)))))
+
+(defun get-field (field)
+  "Parse form and return field FIELD from form"
+  (cond ((integerp field) (nth (1- field) (forms--parse-form)))
+	((stringp field) field)))
+
+(setfun single-line-org-entry (make-org-entry-function "*%s:* %s\n\n"))
+(setfun multi-line-org-entry (make-org-entry-function "\n* %s\n%s\n\n"))
+(setfun single-line-org-entries (mapped 'mapc 'single-line-org-entry))
+(setfun multi-line-org-entries (mapped 'mapc 'multi-line-org-entry))
+
+(defun text-if-non-blank (text)
+  (if (and text (not (string-blank-p text)))
+      (princ (format "%s\n\n" text))))
+
+(defun org-keyword (keyword-value)
+  (princ (format "#+%s: %s\n" (car keyword-value) (cdr keyword-value))))
+
+(defun org-latex-header (header)
+  (org-keyword (cons "LATEX_HEADER" header)))
+
+(setfun org-keywords (mapped 'mapc 'org-keyword))
+(setfun org-latex-headers (mapped 'mapc 'org-latex-header))
+
+(defun forms-print ()
+  (interactive)
+  (find-file (org-to-pdf (funcall form-to-org))))
+
+(defun org-to-pdf (org-source)
+  (let ((tex-file-path (format "%s.tex" (make-temp-file "report"))))
+    (with-temp-buffer
+      (insert org-source)
+      (org-export-to-file 'latex tex-file-path
+	nil nil nil nil export-properties 'org-latex-compile))))
+
+(make-variable-buffer-local 'form-to-org)
diff --git a/ksh-report-header.el b/ksh-report-header.el
new file mode 100644
index 0000000..6194250
--- /dev/null
+++ b/ksh-report-header.el
@@ -0,0 +1,20 @@
+(defun make-report-header (title)
+  (org-keywords
+   '(("TITLE" . "Kuzhanthai Sanjeevi Hospital")
+     ("AUTHOR" . "Dr. Serene Isaac, MD, DGO, DNB")))
+  (org-latex-headers
+   `("\\usepackage{fullpage}"
+     "\\usepackage{nopageno}"
+     "\\usepackage[ddmmyyyy]{datetime}"
+     "\\renewcommand{\\dateseparator}{-}"
+     "\\setlength{\\parindent}{0cm}"
+     "\\usepackage[tiny]{titlesec}"
+     "\\usepackage{wasysym}"
+     "\\usepackage{titling}"
+     ,(format "\\pretitle{\\hrule \\begin{center} {\\Large \\textbf{%s}} \\par \\small \\sc}" title)
+     "\\posttitle{\\par 17, Jawahar Street, Ramavarmapuram, Nagercoil - 629001 \\par \\phone \\, 223374 \\end{center}}"
+     "\\preauthor{}"
+     "\\postauthor{\\par Obstetrician and Gynaecologist}"
+     "\\predate{\\hfill \\textbf{Date: }}"
+     "\\date{\\today}"
+     "\\postdate{\\vspace{1em} \\hrule \\par}")))
diff --git a/ksh-scan2.el b/ksh-scan2.el
new file mode 100644
index 0000000..75f3ae4
--- /dev/null
+++ b/ksh-scan2.el
@@ -0,0 +1,177 @@
+;; -*- lexical-binding: t -*-
+
+(load "ksh-forms-improved.el")
+(load "ksh-report-header.el")
+(load "ksh-biometry.el")
+
+;; Datafile path and fields
+(setq forms-file (expand-file-name "scan2.dat" ksh-data-path))
+(setq forms-number-of-fields
+      (forms-enumerate
+       '(ip-op-no patient-name age sex visit-date lmp-date lmp-edd
+		    scan
+		    maternal-cervix maternal-internal-os
+		    survey-presentation survey-placenta survey-liquor
+		    survey-umbilical-cord survey-fetal-activity survey-cardiac-activity
+		    biometry-bpd biometry-bpd-age biometry-bpd-percentile
+		    biometry-hc biometry-hc-age biometry-hc-percentile
+		    biometry-ac biometry-ac-age biometry-ac-percentile
+		    biometry-fl biometry-fl-age biometry-fl-percentile
+		    biometry-fetal-weight
+		    extended-foot-length extended-tcd
+		    anatomy-intracranial anatomy-neck anatomy-spine
+		    anatomy-face anatomy-lungs anatomy-heart
+		    anatomy-abdominal-situs anatomy-kidneys-bladder
+		    anatomy-long-bones
+		    impression-gestational-age impression-menstrual-age
+		    impression-corrected-edd impression-notes
+		    sonologist)))
+
+;; Default values for fields
+(setq default-field-values
+      `((,sex . "F")
+	(,survey-presentation . "cephalic")
+	(,survey-placenta . "posterior")
+	(,survey-liquor . "normal")
+	(,survey-fetal-activity . "present")
+	(,survey-cardiac-activity . "present")
+	(,anatomy-intracranial . "normal")
+	(,anatomy-neck . "normal. No evidence of significant open neural tube defect")
+	(,anatomy-spine . "normal")
+	(,anatomy-face . "normal")
+	(,anatomy-lungs . "normal")
+	(,anatomy-heart . "normal")
+	(,anatomy-abdominal-situs . "normal")
+	(,anatomy-kidneys-bladder . "normal")
+	(,anatomy-long-bones . "normal for the period of gestation")
+	(,sonologist . "Dr. Bala Bharathy")))
+
+;; Format specification for form display
+(setq forms-format-list
+      (append
+       (list "Trimester 2 - Scan Report\n\n")
+       (form-entries
+	'(("IP/OP No" . ip-op-no)
+	  ("Patient Name" . patient-name)
+	  ("Age" . age)
+	  ("Sex" . sex)
+	  ("Visit Date" . visit-date)
+	  ("LMP Date" . lmp-date)
+	  ("LMP EDD" . lmp-edd)))
+       (list "Indications\n" "Growth Scan\n"
+	     "Real time B-mode ultrasonography of gravid uterus done.\n"
+	     "Route: Transabdominal\n" scan "\n\n")
+       (list "Maternal\n")
+       (form-entries
+	'(("Cervix" . maternal-cervix)
+	  ("Internal os" . maternal-internal-os)))
+       (list "Fetal Survey\n")
+       (form-entries
+	'(("Presentation" . survey-presentation)
+	  ("Placenta" . survey-placenta)
+	  ("Liquor" . survey-liquor)
+	  ("Umbilical cord" . survey-umbilical-cord)
+	  ("Fetal activity" . survey-fetal-activity)
+	  ("Cardiac activity" . survey-cardiac-activity)))
+       (list "Fetal Biometry\n")
+       (form-entries
+	'(("BPD" . biometry-bpd)
+	  ("BPD-Age" . biometry-bpd-age)
+	  ("BPD-Percentile" . biometry-bpd-percentile)
+	  ("HC" . biometry-hc)
+	  ("HC-Age" . biometry-hc-age)
+	  ("HC-Percentile" . biometry-hc-percentile)
+	  ("AC" . biometry-ac)
+	  ("AC-Age" . biometry-ac-age)
+	  ("AC-Percentile" . biometry-ac-percentile)
+	  ("FL" . biometry-fl)
+	  ("FL-Age" . biometry-fl-age)
+	  ("FL-Percentile" . biometry-fl-percentile)
+	  ("Estimated fetal weight according to BPD, HC, AC, FL" . biometry-fetal-weight)))
+       (list "Extended Biometry\n")
+       (form-entries
+	'(("Foot Length" . extended-foot-length)
+	  ("TCD" . extended-tcd)))
+       (list "Fetal Anatomy\n")
+       (form-entries
+	'(("Intracranial structures" . anatomy-intracranial)
+	  ("Neck" . anatomy-neck)
+	  ("Spine" . anatomy-spine)
+	  ("Fetal face" . anatomy-face)
+	  ("Both lungs" . anatomy-lungs)
+	  ("Heart" . anatomy-heart)
+	  ("Abdominal situs" . anatomy-abdominal-situs)
+	  ("Both kidneys and bladder" . anatomy-kidneys-bladder)
+	  ("All long bones" . anatomy-long-bones)))
+       (list "Impression\n")
+       (form-entries
+	'(("Gestational age" . impression-gestational-age)
+	  ("Menstrual age" . impression-menstrual-age)
+	  ("Corrected EDD" . impression-corrected-edd)
+	  ("Notes" . impression-notes)
+	  ("Sonologist" . sonologist)))))
+
+(setq form-to-org
+      (lambda ()
+	(with-output-to-string
+	  (make-report-header "OB - 2/3 TRIMESTER SCAN REPORT")
+	  (single-line-org-entries
+	   `(("IP/OP No" . ,ip-op-no)
+	     ("Age" . ,age)
+	     ("Sex" . ,sex)
+	     ("Name of Patient" . ,patient-name)
+	     ("Visit Date" . ,visit-date)
+	     ("LMP Date" . ,lmp-date)
+	     ("LMP EDD" . ,lmp-edd)))
+	  (mapc 'princ `("* Indications\n" "** Target Scan\n"
+			 "Real time B-mode ultrasonography of gravid uterus done.\n\n"))
+	  (single-line-org-entries
+	   `(("Route" . "Transabdominal")))
+	  (princ (get-field scan))
+	  (princ "\n\n* Maternal\n")
+	  (single-line-org-entries
+	   `(("Cervix" . ,maternal-cervix)
+	     ("Internal os" . ,maternal-internal-os)))
+	  (princ "* Fetal Survey\n")
+	  (single-line-org-entries
+	   `(("Presentation" . ,survey-presentation)
+	     ("Placenta" . ,survey-placenta)
+	     ("Liquor" . ,survey-liquor)
+	     ("Umbilical cord" . ,survey-umbilical-cord)
+	     ("Fetal activity" . ,survey-fetal-activity)
+	     ("Cardiac activity" . ,survey-cardiac-activity)))
+	  (princ "* Fetal Biometry\n")
+	  (insert-biometry
+	   `(("BPD" ,biometry-bpd ,biometry-bpd-age ,biometry-bpd-percentile)
+	     ("HC" ,biometry-hc ,biometry-hc-age ,biometry-hc-percentile)
+	     ("AC" ,biometry-ac ,biometry-ac-age ,biometry-ac-percentile)
+	     ("FL" ,biometry-fl ,biometry-fl-age ,biometry-fl-percentile)))
+	  (princ "* Extended Biometry\n")
+	  (single-line-org-entries
+	   `(("Foot Length" . ,extended-foot-length)
+	     ("TCD" . ,extended-tcd)))
+	  (princ "* Fetal Anatomy\n")
+	  (single-line-org-entries
+	   `(("Intracranial structures" . ,anatomy-intracranial)
+	     ("Neck" . ,anatomy-neck)
+	     ("Spine" . ,anatomy-spine)
+	     ("Fetal face" . ,anatomy-face)
+	     ("Both lungs" . ,anatomy-lungs)
+	     ("Heart" . ,anatomy-heart)
+	     ("Abdominal situs" . ,anatomy-abdominal-situs)
+	     ("Both kidneys and bladder" . ,anatomy-kidneys-bladder)
+	     ("All long bones" . ,anatomy-long-bones)))
+	  (princ "* Impression\n")
+	  (princ (format "%s corresponding to a gestational age of %s\n\n"
+			 (get-field scan) (get-field impression-gestational-age)))
+	  (princ "Gestational age assigned as per biometry (CRL)\n\n")
+	  (single-line-org-entries
+	   `(("Menstrual age" . ,impression-menstrual-age)
+	     ("Corrected EDD" . ,impression-corrected-edd)
+	     ("Placenta" . ,survey-placenta)
+	     ("Presentation" . ,survey-presentation)
+	     ("Liquor" . ,survey-liquor)
+	     ("Estimated fetal weight according to BPD, HC, AC, FL" . ,biometry-fetal-weight)))
+	  (text-if-non-blank (get-field impression-notes))
+	  (multi-line-org-entries
+	   `(("Sonologist" . ,sonologist))))))
diff --git a/ksh-scan3.el b/ksh-scan3.el
new file mode 100644
index 0000000..83018c5
--- /dev/null
+++ b/ksh-scan3.el
@@ -0,0 +1,130 @@
+;; -*- lexical-binding: t -*-
+
+(load "ksh-forms-improved.el")
+(load "ksh-report-header.el")
+(load "ksh-biometry.el")
+
+;; Datafile path and fields
+(setq forms-file (expand-file-name "scan3.dat" ksh-data-path))
+(setq forms-number-of-fields
+      (forms-enumerate
+       '(ip-op-no patient-name age sex visit-date lmp-date lmp-edd
+		  scan
+		  survey-presentation survey-placenta survey-liquor
+		  survey-amniotic-fluid-index survey-umbilical-cord
+		  survey-fetal-activity survey-cardiac-activity
+		  survey-fetal-heart-rate
+		  biometry-bpd biometry-bpd-age biometry-bpd-percentile
+		  biometry-hc biometry-hc-age biometry-hc-percentile
+		  biometry-ac biometry-ac-age biometry-ac-percentile
+		  biometry-fl biometry-fl-age biometry-fl-percentile
+		  biometry-fetal-weight
+		  impression-gestational-age impression-menstrual-age
+		  impression-corrected-edd impression-notes
+		  sonologist)))
+
+;; Default values for fields
+(setq default-field-values
+      `((,sex . "F")
+	(,survey-presentation . "cephalic")
+	(,survey-placenta . "posterior")
+	(,survey-liquor . "normal")
+	(,survey-fetal-activity . "normal")
+	(,survey-cardiac-activity . "normal")
+	(,sonologist . "Dr. Bala Bharathy")))
+
+;; Format specification for form display
+(setq forms-format-list
+      (append
+       (list "Trimester 3 - Scan Report\n\n")
+       (form-entries
+	'(("IP/OP No" . ip-op-no)
+	  ("Patient Name" . patient-name)
+	  ("Age" . age)
+	  ("Sex" . sex)
+	  ("Visit Date" . visit-date)
+	  ("LMP Date" . lmp-date)
+	  ("LMP EDD" . lmp-edd)))
+       (list "Indications\n" "Growth Scan\n"
+	     "Real time B-mode ultrasonography of gravid uterus done.\n"
+	     "Route: Transabdominal\n" scan "\n\n")
+       (list "Fetal Survey\n")
+       (form-entries
+	'(("Presentation" . survey-presentation)
+	  ("Placenta" . survey-placenta)
+	  ("Liquor" . survey-liquor)
+	  ("Amniotic fluid index" . survey-amniotic-fluid-index)
+	  ("Umbilical cord" . survey-umbilical-cord)
+	  ("Fetal activity" . survey-fetal-activity)
+	  ("Cardiac activity" . survey-cardiac-activity)
+	  ("Fetal heart rate" . survey-fetal-heart-rate)))
+       (list "Fetal Biometry\n")
+       (form-entries
+	'(("BPD" . biometry-bpd)
+	  ("BPD-Age" . biometry-bpd-age)
+	  ("BPD-Percentile" . biometry-bpd-percentile)
+	  ("HC" . biometry-hc)
+	  ("HC-Age" . biometry-hc-age)
+	  ("HC-Percentile" . biometry-hc-percentile)
+	  ("AC" . biometry-ac)
+	  ("AC-Age" . biometry-ac-age)
+	  ("AC-Percentile" . biometry-ac-percentile)
+	  ("FL" . biometry-fl)
+	  ("FL-Age" . biometry-fl-age)
+	  ("FL-Percentile" . biometry-fl-percentile)
+	  ("Estimated fetal weight according to BPD, HC, AC, FL" . biometry-fetal-weight)))
+       (list "Impression\n")
+       (form-entries
+	'(("Gestational age" . impression-gestational-age)
+	  ("Menstrual age" . impression-menstrual-age)
+	  ("Corrected EDD" . impression-corrected-edd)
+	  ("Notes" . impression-notes)
+	  ("Sonologist" . sonologist)))))
+
+(setq form-to-org
+      (lambda ()
+	(with-output-to-string
+	  (make-report-header "OB - 3/3 TRIMESTER SCAN REPORT")
+	  (single-line-org-entries
+	   `(("IP/OP No" . ,ip-op-no)
+	     ("Age" . ,age)
+	     ("Sex" . ,sex)
+	     ("Name of Patient" . ,patient-name)
+	     ("Visit Date" . ,visit-date)
+	     ("LMP Date" . ,lmp-date)
+	     ("LMP EDD" . ,lmp-edd)))
+	  (mapc 'princ `("* Indications\n" "** Growth Scan\n"
+			 "Real time B-mode ultrasonography of gravid uterus done.\n\n"))
+	  (single-line-org-entries
+	   `(("Route" . "Transabdominal")))
+	  (text-if-non-blank (get-field scan))
+	  (princ "* Fetal Survey\n")
+	  (single-line-org-entries
+	   `(("Presentation" . ,survey-presentation)
+	     ("Placenta" . ,survey-placenta)
+	     ("Liquor" . ,survey-liquor)
+	     ("Amniotic fluid index" . ,survey-amniotic-fluid-index)
+	     ("Umbilical cord" . ,survey-umbilical-cord)
+	     ("Fetal activity" . ,survey-fetal-activity)
+	     ("Cardiac activity" . ,survey-cardiac-activity)
+	     ("Fetal heart rate" . ,survey-fetal-heart-rate)))
+	  (princ "* Fetal Biometry\n")
+	  (insert-biometry
+	   `(("BPD" ,biometry-bpd ,biometry-bpd-age ,biometry-bpd-percentile)
+	     ("HC" ,biometry-hc ,biometry-hc-age ,biometry-hc-percentile)
+	     ("AC" ,biometry-ac ,biometry-ac-age ,biometry-ac-percentile)
+	     ("FL" ,biometry-fl ,biometry-fl-age ,biometry-fl-percentile)))
+	  (princ "* Impression\n")
+	  (princ (format "%s corresponding to a gestational age of %s\n\n"
+			 (get-field scan) (get-field impression-gestational-age)))
+	  (princ "Gestational age assigned as per biometry (CRL)\n\n")
+	  (single-line-org-entries
+	   `(("Menstrual age" . ,impression-menstrual-age)
+	     ("Corrected EDD" . ,impression-corrected-edd)
+	     ("Placenta" . ,survey-placenta)
+	     ("Presentation" . ,survey-presentation)
+	     ("Liquor" . ,survey-liquor)
+	     ("Estimated fetal weight according to BPD, HC, AC, FL" . ,biometry-fetal-weight)))
+	  (text-if-non-blank (get-field impression-notes))
+	  (multi-line-org-entries
+	   `(("Sonologist" . ,sonologist))))))
diff --git a/scan-images.sh b/scan-images.sh
new file mode 100755
index 0000000..b538cae
--- /dev/null
+++ b/scan-images.sh
@@ -0,0 +1,19 @@
+#! /bin/bash -xe
+
+# Query user for directory containing the scan images
+IMAGE_DIRECTORY=$(zenity --file-selection --directory --text="Choose folder with ultrasound images")
+
+# If dialog box was cancelled, exit and do nothing
+if [[ -z $IMAGE_DIRECTORY ]]
+then
+    exit
+fi
+
+IMAGE_FILES=$(ls $IMAGE_DIRECTORY/*.tif)
+IMAGE_WIDTH=$(identify -format "%w" $(ls $IMAGE_DIRECTORY/*.tif | head -n 1))
+MONTAGE_OUTFILE=$(mktemp --suffix=".pdf")
+
+# Create montage of scan images
+montage -geometry $IMAGE_WIDTH -tile 2x3 $IMAGE_FILES $MONTAGE_OUTFILE
+# Send to printer, and delete from memory
+lpr -r -o sides=two-sided-long-edge -o fit-to-page -o page-left=72 -o page-right=72 $MONTAGE_OUTFILE