Repository: chrovis/cljam
Pull request #197 (2020-05-29 03:25:24)

Create an index file while writing a sorted BAM file
This PR is to add a function to create an index file if it is already sorted when creating a BAM file.
PR for issue #141 .

codecov[bot]: # [Codecov](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=h1) Report
> Merging [#197](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=desc) into [master](https://codecov.io/gh/chrovis/cljam/commit/c479aaef77345d58915cbf31a0d40840b59e9d31&el=desc) will **increase** coverage by `0.25%`.
> The diff coverage is `83.82%`.

[![Impacted file tree graph](https://codecov.io/gh/chrovis/cljam/pull/197/graphs/tree.svg?width=650&height=150&src=pr&token=hrCuiz6NWa)](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #197      +/-   ##
==========================================
+ Coverage   86.85%   87.11%   +0.25%     
==========================================
  Files          77       77              
  Lines        6223     6333     +110     
  Branches      520      528       +8     
==========================================
+ Hits         5405     5517     +112     
- Misses        298      302       +4     
+ Partials      520      514       -6     
```


| [Impacted Files](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/cljam/io/bam/writer.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2lvL2JhbS93cml0ZXIuY2xq) | `73.68% <78.00%> (+7.96%)` | :arrow_up: |
| [src/cljam/io/bam/core.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2lvL2JhbS9jb3JlLmNsag==) | `100.00% <100.00%> (ø)` | |
| [src/cljam/io/bam\_index/writer.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2lvL2JhbV9pbmRleC93cml0ZXIuY2xq) | `85.85% <100.00%> (+1.48%)` | :arrow_up: |
| [src/cljam/algo/vcf\_indexer.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2FsZ28vdmNmX2luZGV4ZXIuY2xq) | `100.00% <0.00%> (ø)` | |
| [src/cljam/io/csi.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2lvL2NzaS5jbGo=) | `90.42% <0.00%> (+0.53%)` | :arrow_up: |
| [src/cljam/io/bcf/reader.clj](https://codecov.io/gh/chrovis/cljam/pull/197/diff?src=pr&el=tree#diff-c3JjL2NsamFtL2lvL2JjZi9yZWFkZXIuY2xq) | `94.28% <0.00%> (+4.50%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=footer). Last update [c479aae...780b3a7](https://codecov.io/gh/chrovis/cljam/pull/197?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

alumi: Hi @xckitahara, Thank you for working on this feature!
Could you squash your commits into some concise and meaningful ones and then rebase them onto the master branch?
alumi: It seems like GitHub cannot associate your commits with your account @xckitahara.
Please configure your git client with [`git config --global user.email`](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_your_identity).
xckitahara: > Hi @xckitahara, Thank you for working on this feature!
> Could you squash your commits into some concise and meaningful ones and then rebase them onto the master branch?

@alumi Has been updated.
xckitahara: > It seems like GitHub cannot associate your commits with your account @xckitahara.
> Please configure your git client with [`git config --global user.email`](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_your_identity).

I'm sorry.
Set up.
alumi: > Set up
Great :+1:
Now you need to amend `—reset-author` commits to fix the committer and the author
xckitahara: Performance comparison

Time measurement of sort:
① before change,
② after change (with bai created),
③ after change (without bai created)
It was measured at. (Since sort is used for both aln write and block write)
The target file is medium-sam-file used in the test etc.

① time: 761.079912 ms, sd: 98.673396 µs
② time: 925.187657 ms, sd: 47.099024 µs
③ time: 741.354834 ms, sd: 76.992721 µs


Patch:
diff --git a/src/cljam/algo/convert.clj b/src/cljam/algo/convert.clj
index 914c620..24abbfa 100644
--- a/src/cljam/algo/convert.clj
+++ b/src/cljam/algo/convert.clj
@@ -46,10 +46,10 @@
 
 (defn convert-sam
   "Converts file format between SAM and BAM based on the file extension."
-  [in out & {:keys [n-threads num-block]
-             :or {n-threads 0, num-block default-num-block}}]
+  [in out & {:keys [n-threads num-block create-index?]
+             :or {n-threads 0, num-block default-num-block, create-index? false}}]
   (with-open [rdr (sam/reader in)
-              wtr (sam/writer out)]
+              wtr (sam/writer out create-index?)]
     (binding [*n-threads* n-threads]
       (cond
         (io-util/sam-writer? wtr) (convert-sam* rdr wtr num-block sam-write-alignments)
diff --git a/src/cljam/io/bam/core.clj b/src/cljam/io/bam/core.clj
index 2183103..92ae3a3 100644
--- a/src/cljam/io/bam/core.clj
+++ b/src/cljam/io/bam/core.clj
@@ -52,6 +52,13 @@
 ;; Writing
 ;; -------
 
-(defn ^BAMWriter writer [f]
-  (BAMWriter. (util/as-url f)
-              (DataOutputStream. (bgzf/bgzf-output-stream f))))
+(defn ^BAMWriter writer
+  ([f] (writer f false))
+  ([f create-index?]
+   (let [index (if create-index? {:no-coordinate-alns 0} false)
+         w (bgzf/bgzf-output-stream f)]
+     (BAMWriter. (util/as-url f)
+                 w
+                 (DataOutputStream. w)
+                 (atom nil)
+                 (atom index)))))
diff --git a/src/cljam/io/bam/writer.clj b/src/cljam/io/bam/writer.clj
index b97e2b2..cf03aad 100644
--- a/src/cljam/io/bam/writer.clj
+++ b/src/cljam/io/bam/writer.clj
@@ -1,23 +1,44 @@
 (ns cljam.io.bam.writer
   "Writer of BAM file format."
-  (:require [cljam.io.protocols :as protocols]
+  (:require [clojure.java.io :as cio]
+            [cljam.io.protocols :as protocols]
             [cljam.io.sam.util.refs :as refs]
             [cljam.io.sam.util.header :as header]
             [cljam.io.util.lsb :as lsb]
             [cljam.io.bam.common :as common]
-            [cljam.io.bam.encoder :as encoder])
-  (:import [java.io Closeable]))
+            [cljam.io.bam.encoder :as encoder]
+            [cljam.io.bam.decoder :as bam-decoder]
+            [cljam.io.bam-index.writer :as bai-writer])
+  (:import [java.io ByteArrayOutputStream Closeable DataOutputStream]
+           [cljam.io.bam.decoder BAMRawBlock]
+           [bgzf4j BGZFOutputStream]))
 
 (declare write-header* write-refs* write-alignments* write-blocks*)
 
+(defn- sort-by-pos?
+  "Returns true if the bam is sorted by coordinate, false if not.
+   It is detected by`@HD SO:coordinate` tag in the header."
+  [header]
+  (let [so (:SO (:HD header))]
+    (= so (name :coordinate))))
+
 ;;
 ;; BAMWriter
 ;;
 
-(deftype BAMWriter [url writer]
+(deftype BAMWriter [url writer data-writer refs index]
   Closeable
   (close [this]
-    (.close ^Closeable (.writer this)))
+    (.close ^Closeable (.data-writer this))
+    (when-let [idx @index]
+      (let [last-pointer (.getFilePointer ^BGZFOutputStream writer)
+            n-refs (count @refs)]
+        (with-open [w (->> (str url ".bai")
+                           cio/output-stream
+                           DataOutputStream.)]
+          (->> (bai-writer/update-last-pointer idx last-pointer)
+               (bai-writer/finalize-index n-refs)
+               (bai-writer/write-index*! w n-refs))))))
   protocols/IWriter
   (writer-url [this]
     (.url this))
@@ -36,15 +57,18 @@
 ;;
 
 (defn write-header* [^BAMWriter wtr header]
-  (let [w (.writer wtr)
+  (swap! (.index wtr) #(and (sort-by-pos? header) %))
+  (let [w (.data-writer wtr)
         header-string (str (header/stringify-header header) \newline)]
     (lsb/write-bytes w (.getBytes ^String common/bam-magic)) ; magic
     (lsb/write-int w (count header-string))
     (lsb/write-string w header-string)))
 
 (defn write-refs* [^BAMWriter wtr header]
-  (let [w (.writer wtr)
+  (let [w (.data-writer wtr)
         refs (refs/make-refs header)]
+    (when @(.index wtr)
+      (reset! (.refs wtr) refs))
     (lsb/write-int w (count refs))
     (doseq [ref refs]
       (lsb/write-int w (inc (count (:name ref))))
@@ -53,14 +77,50 @@
       (lsb/write-int w (:len ref)))))
 
 (defn write-alignments* [^BAMWriter wtr alns header]
-  (let [w (.writer wtr)
+  (let [dw (.data-writer wtr)
+        w ^BGZFOutputStream (.writer wtr)
         refs (refs/make-refs header)]
-    (doseq [a alns]
-      (lsb/write-int w (encoder/get-block-size a))
-      (encoder/encode-alignment w a refs))))
+    (with-open [baos (ByteArrayOutputStream. 4096)
+                dos (DataOutputStream. baos)]
+      (let [pointer-block (map
+                           (fn [a]
+                             (let [pointer-beg (.getFilePointer w)]
+                               (.reset baos)
+                               (encoder/encode-alignment dos a refs)
+                               (lsb/write-int dw (.size baos))
+                               (.writeTo baos dw)
+                               (when @(.index wtr)
+                                 (bam-decoder/decode-pointer-block
+                                  (BAMRawBlock. (.toByteArray baos)
+                                                pointer-beg
+                                                (.getFilePointer w))))))
+                           alns)]
+        (if @(.index wtr)
+          (reset! (.index wtr)
+                  (->> pointer-block
+                       bai-writer/make-index*
+                       (bai-writer/merge-index @(.index wtr))))
+          (dorun pointer-block))
+        nil))))
 
 (defn write-blocks* [^BAMWriter wtr blocks]
-  (let [w (.writer wtr)]
-    (doseq [b blocks]
-      (lsb/write-int w (alength ^bytes (:data b)))
-      (lsb/write-bytes w (:data b)))))
+  (let [dw (.data-writer wtr)
+        w ^BGZFOutputStream (.writer wtr)
+        pointer-block (map
+                       (fn [{:keys [data]}]
+                         (let [pointer-beg (.getFilePointer w)]
+                           (lsb/write-int dw (alength ^bytes data))
+                           (lsb/write-bytes dw data)
+                           (when @(.index wtr)
+                             (bam-decoder/decode-pointer-block
+                              (BAMRawBlock. data
+                                            pointer-beg
+                                            (.getFilePointer w))))))
+                       blocks)]
+    (if @(.index wtr)
+      (reset! (.index wtr)
+              (->> pointer-block
+                   bai-writer/make-index*
+                   (bai-writer/merge-index @(.index wtr))))
+      (dorun pointer-block))
+    nil))
diff --git a/src/cljam/io/bam_index/writer.clj b/src/cljam/io/bam_index/writer.clj
index f0612e5..d8f23ab 100644
--- a/src/cljam/io/bam_index/writer.clj
+++ b/src/cljam/io/bam_index/writer.clj
@@ -14,8 +14,6 @@
            [cljam.io.bam.decoder BAMPointerBlock]
            [cljam.io.util.chunk Chunk]))
 
-(declare make-index-from-blocks)
-
 ;; BAIWriter
 ;; ---------
 
@@ -111,33 +109,6 @@
                 (update-bin-index (.bin-index index-status) aln)
                 (update-linear-index (.linear-index index-status) aln)))
 
-;; ### Making index
-
-(defn- make-index*
-  "Calculates index from the references and alignments, returning it as a map.
-  Returned index is still intermediate. It must be passed to finalize function
-  in the final stage."
-  [alns]
-  (loop [[^BAMPointerBlock aln & rest] alns
-         rid (.ref-id aln)
-         idx-status (init-index-status)
-         no-coordinate-alns 0
-         indices {}]
-    (if aln
-      (let [rid' (.ref-id aln)
-            new-ref? (not= rid' rid)
-            idx-status' (update-index-status
-                         (if new-ref? (init-index-status) idx-status) aln)
-            no-coordinate-alns' (if (zero? (.pos aln))
-                                  (inc no-coordinate-alns)
-                                  no-coordinate-alns)
-            indices' (if new-ref?
-                       (assoc indices rid idx-status)
-                       indices)]
-        (recur rest rid' idx-status' no-coordinate-alns' indices'))
-      (assoc indices rid idx-status
-             :no-coordinate-alns no-coordinate-alns))))
-
 ;; Merging indices
 ;; -------------
 
@@ -174,20 +145,6 @@
   [lidx1 lidx2]
   (merge-with min lidx1 lidx2))
 
-(defn- merge-index
-  "Merges two intermediate indices, returning the merged intermediate index."
-  [idx1 idx2]
-  (let [no-coordinate-alns (+ (:no-coordinate-alns idx1) (:no-coordinate-alns idx2))
-        idx1 (dissoc idx1 :no-coordinate-alns)
-        idx2 (dissoc idx2 :no-coordinate-alns)]
-    (-> (merge-with
-         (fn [^IndexStatus v1 ^IndexStatus v2]
-           (IndexStatus. (merge-meta-data (.meta-data v1) (.meta-data v2))
-                         (merge-bin-index (.bin-index v1) (.bin-index v2))
-                         (merge-linear-index (.linear-index v1) (.linear-index v2))))
-         idx1 idx2)
-        (assoc :no-coordinate-alns no-coordinate-alns))))
-
 ;; Finalizing index
 ;; ----------------
 
@@ -218,20 +175,6 @@
        (complement-linear-index)
        (map second)))
 
-(defn- finalize-index
-  "Converts intermediate BAM index data structure into final one. Must be called
-  in the final stage."
-  [^long nrefs index]
-  (loop [i 0
-         index index]
-    (if (< i nrefs)
-      (if (get index i)
-        (recur (inc i) (-> index
-                           (update-in [i :bin-index] finalize-bin-index)
-                           (update-in [i :linear-index] finalize-linear-index)))
-        (recur (inc i) index))
-      index)))
-
 ;; Writing index
 ;; -----------
 
@@ -253,33 +196,6 @@
   (lsb/write-long w (:aligned-alns meta-data))
   (lsb/write-long w (:unaligned-alns meta-data)))
 
-(defn- write-index*!
-  [wtr ^long nrefs alns]
-  ;; magic
-  (lsb/write-bytes wtr (.getBytes ^String bai-magic))
-  ;; n_ref
-  (lsb/write-int wtr nrefs)
-  (let [indices (make-index-from-blocks nrefs alns)]
-    (dotimes [i nrefs]
-      (let [index (get indices i)
-            n-bin (count (:bin-index index))]
-        ;; bins
-        (if (zero? n-bin)
-          (lsb/write-int wtr 0)
-          (do
-            ;; # of bins
-            (lsb/write-int wtr (inc n-bin))
-            (doseq [bin (:bin-index index)]
-              (write-bin wtr (:bin bin) (:chunks bin)))
-            ;; meta data
-            (write-meta-data wtr (:meta-data index))))
-        ;; linear index
-        (lsb/write-int wtr (count (:linear-index index)))
-        (doseq [l (:linear-index index)]
-          (lsb/write-long wtr l))))
-    ;; no coordinate alignments
-    (lsb/write-long wtr (:no-coordinate-alns indices))))
-
 ;; Public
 ;; ------
 
@@ -288,6 +204,65 @@
   effect on performance of concurrent indexing. The default value is 10,000."
   10000)
 
+;; Merging indices
+;; -------------
+
+(defn merge-index
+  "Merges two intermediate indices, returning the merged intermediate index."
+  [idx1 idx2]
+  (let [no-coordinate-alns (+ (:no-coordinate-alns idx1) (:no-coordinate-alns idx2))
+        idx1 (dissoc idx1 :no-coordinate-alns)
+        idx2 (dissoc idx2 :no-coordinate-alns)]
+    (-> (merge-with
+         (fn [^IndexStatus v1 ^IndexStatus v2]
+           (IndexStatus. (merge-meta-data (.meta-data v1) (.meta-data v2))
+                         (merge-bin-index (.bin-index v1) (.bin-index v2))
+                         (merge-linear-index (.linear-index v1) (.linear-index v2))))
+         idx1 idx2)
+        (assoc :no-coordinate-alns no-coordinate-alns))))
+
+;; Making index
+;; -----------
+
+(defn finalize-index
+  "Converts intermediate BAM index data structure into final one. Must be called
+  in the final stage."
+  [^long nrefs index]
+  (loop [i 0
+         index index]
+    (if (< i nrefs)
+      (if (get index i)
+        (recur (inc i) (-> index
+                           (update-in [i :bin-index] finalize-bin-index)
+                           (update-in [i :linear-index] finalize-linear-index)))
+        (recur (inc i) index))
+      index)))
+
+(defn make-index*
+  "Calculates index from the references and alignments, returning it as a map.
+  Returned index is still intermediate. It must be passed to finalize function
+  in the final stage."
+  [alns]
+  (loop [[^BAMPointerBlock aln & rest] alns
+         rid (.ref-id aln)
+         idx-status (init-index-status)
+         no-coordinate-alns 0
+         indices {}]
+    (if aln
+      (let [rid' (.ref-id aln)
+            new-ref? (not= rid' rid)
+            idx-status' (update-index-status
+                         (if new-ref? (init-index-status) idx-status) aln)
+            no-coordinate-alns' (if (zero? (.pos aln))
+                                  (inc no-coordinate-alns)
+                                  no-coordinate-alns)
+            indices' (if new-ref?
+                       (assoc indices rid idx-status)
+                       indices)]
+        (recur rest rid' idx-status' no-coordinate-alns' indices'))
+      (assoc indices rid idx-status
+             :no-coordinate-alns no-coordinate-alns))))
+
 (defn make-index-from-blocks
   "Calculates a BAM index from provided references and alignment blocks.
   Optionally, you can do this process concurrently."
@@ -310,7 +285,57 @@
          make-index-fn
          (finalize-index nrefs))))
 
+(defn update-last-pointer
+  "Update the last pointer of the index to the given value."
+  [index eof-ptr]
+  (if (or (= (keys index) [:no-coordinate-alns])
+          (pos? (get index :no-coordinate-alns 0)))
+    index
+    (let [last-ref (apply max (keys (dissoc index :no-coordinate-alns)))
+          last-key (->> (for [[bin chunks] (get-in index [last-ref :bin-index])
+                              [i {:keys [end]}] (map-indexed vector chunks)]
+                          [end [last-ref :bin-index bin i :end]])
+                        (apply max-key first)
+                        last)]
+      (-> index
+          (assoc-in [last-ref :meta-data :last-offset] eof-ptr)
+          (assoc-in last-key eof-ptr)))))
+
+;; Writing index
+;; -----------
+
+(defn write-index*!
+  "Write the index to a file."
+  [wtr ^long nrefs indices]
+  ;; magic
+  (lsb/write-bytes wtr (.getBytes ^String bai-magic))
+  ;; n_ref
+  (lsb/write-int wtr nrefs)
+  (dotimes [i nrefs]
+    (let [index (get indices i)
+          n-bin (count (:bin-index index))]
+        ;; bins
+      (if (zero? n-bin)
+        (lsb/write-int wtr 0)
+        (do
+            ;; # of bins
+          (lsb/write-int wtr (inc n-bin))
+          (doseq [bin (:bin-index index)]
+            (write-bin wtr (:bin bin) (:chunks bin)))
+            ;; meta data
+          (write-meta-data wtr (:meta-data index))))
+        ;; linear index
+      (lsb/write-int wtr (count (:linear-index index)))
+      (doseq [l (:linear-index index)]
+        (lsb/write-long wtr l))))
+    ;; no coordinate alignments
+  (lsb/write-long wtr (:no-coordinate-alns indices)))
+
+;; ------
+
 (defn write-index!
   "Calculates a BAM index from alns, writing the index to a file."
   [^BAIWriter wtr alns]
-  (write-index*! (.writer wtr) (count (.refs wtr)) alns))
+  (let [nrefs (count (.refs wtr))
+        indices (make-index-from-blocks nrefs alns)]
+    (write-index*! (.writer wtr) nrefs indices)))
diff --git a/src/cljam/io/sam.clj b/src/cljam/io/sam.clj
index bd0dde2..7a45d4f 100644
--- a/src/cljam/io/sam.clj
+++ b/src/cljam/io/sam.clj
@@ -89,17 +89,23 @@
 (defn ^BAMWriter bam-writer
   "Returns an open cljam.io.bam.writer.BAMWriter of f. Should be used inside
   with-open to ensure the writer is properly closed."
-  [f]
-  (bam-core/writer f))
+  ([f]
+   (bam-writer f false))
+  ([f create-index?]
+   (bam-core/writer f create-index?)))
 
 (defn ^Closeable writer
   "Selects suitable writer from f's extension, returning the writer. This
   function supports SAM and BAM format."
-  [f]
-  (case (io-util/file-type f)
-    :sam (sam-writer f)
-    :bam (bam-writer f)
-    (throw (IllegalArgumentException. "Invalid file type"))))
+  ([f]
+   (writer f false))
+  ([f create-index?]
+   (case (io-util/file-type f)
+     :sam (if create-index?
+            (throw (ex-info "SAM file indexing is not implemented." {}))
+            (sam-writer f))
+     :bam (bam-writer f create-index?)
+     (throw (IllegalArgumentException. "Invalid file type")))))
 
 (defn write-header
   "Writes header to the SAM/BAM file."
diff --git a/src/cljam/io/util/bin.clj b/src/cljam/io/util/bin.clj
index 228d5dc..fceb279 100644
--- a/src/cljam/io/util/bin.clj
+++ b/src/cljam/io/util/bin.clj
@@ -1,75 +1,79 @@
 (ns cljam.io.util.bin
   (:require [cljam.io.util.chunk :as util-chunk]))
 
-(defn max-pos
-  "Returns a maximum position of a binning index. The value is identical to the
-  width of bin 0."
-  ^long [^long min-shift ^long depth]
+(defn max-pos ^long [^long min-shift ^long depth]
   (bit-shift-left 1 (+ min-shift (* 3 depth))))
 
-(defn first-bin-of-level
-  "Returns a left-most bin number of the given `level`."
-  ^long [^long level]
+(defn- reg->bins*
+  "Returns candidate bins for the specified region as a vector."
+  [^long beg ^long end ^long min-shift ^long depth]
+  (let [max-pos (max-pos min-shift depth)
+        beg (if (<= beg 0) 0 (min (dec beg) max-pos))
+        end (if (<= end 0) max-pos (min end max-pos))]
+    (into [0]
+          (mapcat
+           (fn [^long d]
+             (let [t
+                   (apply + (map (fn [^long x] (bit-shift-left 1 (* x 3)))
+                                 (range (inc d))))
+                   s (+ min-shift (* 3 (- depth d 1)))]
+               (range (+ t (bit-shift-right beg s))
+                      (+ t 1 (bit-shift-right (dec end) s))))))
+          (range depth))))
+
+(def ^:private reg->bins (memoize reg->bins*))
+
+(defn first-bin-of-level ^long [^long level]
   (quot (bit-shift-left 1 (* 3 level)) 7))
 
-(defn bin-width-of-level
-  "Returns a width shared by bins of the same given `level`."
-  ^long [^long level ^long min-shift ^long depth]
+(defn bin-width-of-level ^long [^long level ^long min-shift ^long depth]
   (bit-shift-left 1 (+ min-shift (* 3 (- depth level)))))
 
-(defn bin-level
-  "Returns a level that the given `bin` belongs to."
-  ^long [^long bin]
+(defn bin-level ^long [^long bin]
   (let [x (inc (quot (- 64 (Long/numberOfLeadingZeros bin)) 3))]
     (cond-> x (< bin (first-bin-of-level x)) dec)))
 
-(defn bin-beg
-  "Returns a beginning position of the given `bin`. 1-based."
-  ^long [^long bin ^long min-shift ^long depth]
+(defn bin-beg ^long [^long bin ^long min-shift ^long depth]
   (let [level (bin-level bin)]
     (inc (* (- bin (first-bin-of-level level))
             (bin-width-of-level level min-shift depth)))))
 
-(defn max-bin
-  "Returns a maximum bin number of a binning index with the given `depth`."
-  ^long [^long depth]
+(defn max-bin ^long [^long depth]
   (dec (first-bin-of-level (inc depth))))
 
-(defn leading-bins-at-level
-  "Returns the distance between the bin corresponding to `pos` and the first one
-  at the same level."
-  ^long [^long pos ^long level ^long min-shift ^long depth]
-  (unsigned-bit-shift-right pos (+ min-shift (* (- depth level) 3))))
+(defprotocol IBinningIndex
+  (get-chunks [this ref-idx bins])
+  (get-min-offset [this ref-idx beg])
+  (get-min-shift [this])
+  (get-depth [this])
+  (get-chr-names  [this]))
 
 (defn pos->lidx-offset
-  "Returns an offset of a linear index that the given `pos` belongs to."
   [^long pos ^long linear-index-shift]
   (bit-shift-right (if (<= pos 0) 0 (dec pos)) linear-index-shift))
 
-(defn reg->bins
-  "Returns all overlapping bins for the specified region [`beg`, `end`] as a
-  vector."
-  [^long beg ^long end ^long min-shift ^long depth]
-  (let [max-pos (max-pos min-shift depth)
-        beg (dec (Math/min max-pos (Math/max 1 beg)))
-        end (dec (Math/min max-pos (Math/max 1 end)))]
-    (into [0]
-          (mapcat
-           (fn [^long d]
-             (let [t (long (transduce
-                            (map (fn [^long x] (bit-shift-left 1 (* x 3))))
-                            + 0 (range (inc d))))
-                   s (+ min-shift (* 3 (- depth d 1)))]
-               (range (+ t (bit-shift-right beg s))
-                      (+ t 1 (bit-shift-right end s))))))
-          (range depth))))
+(defn get-spans
+  "Calculates span information for random access from index data such as tabix."
+  [index-data ^long ref-idx ^long beg ^long end]
+  (let [bins
+        (reg->bins beg end (get-min-shift index-data) (get-depth index-data))
+        chunks (get-chunks index-data ref-idx bins)
+        min-offset (get-min-offset index-data ref-idx beg)]
+    (->> (util-chunk/optimize-chunks chunks min-offset)
+         (map vals))))
+
+(defn leading-bins-at-level
+  "Returns the distance between the binning corresponding to pos and
+   the first one at the passed depth"
+  ^long [^long pos ^long level ^long min-shift ^long depth]
+  (unsigned-bit-shift-right pos (+ min-shift (* (- depth level) 3))))
 
 (defn reg->bin
-  "Calculates the smallest bin containing the given region [`beg`, `end`]."
+  "Calculates bin given an alignment covering [beg, end]"
   ^long [^long beg ^long end ^long min-shift ^long depth]
   (let [max-pos (max-pos min-shift depth)
-        beg (dec (Math/min max-pos (Math/max 1 beg)))
-        end (dec (Math/min max-pos (Math/max 1 end)))]
+        beg (dec (Math/min max-pos (Math/max 1  beg)))
+        end (dec (Math/min max-pos (Math/max 1  end)))]
     (loop [level depth]
       (if-not (neg? level)
         (let [beg-bins (leading-bins-at-level beg level min-shift depth)]
@@ -77,20 +81,3 @@
             (+ (first-bin-of-level level) beg-bins)
             (recur (dec level))))
         0))))
-
-(defprotocol IBinningIndex
-  (get-chunks [this ref-idx bins])
-  (get-min-offset [this ref-idx beg])
-  (get-min-shift [this])
-  (get-depth [this])
-  (get-chr-names [this]))
-
-(defn get-spans
-  "Calculates span information for random access from index data such as tabix."
-  [index-data ^long ref-idx ^long beg ^long end]
-  (let [bins (reg->bins
-              beg end (get-min-shift index-data) (get-depth index-data))
-        chunks (get-chunks index-data ref-idx bins)
-        min-offset (get-min-offset index-data ref-idx beg)]
-    (->> (util-chunk/optimize-chunks chunks min-offset)
-         (map vals))))
diff --git a/src/cljam/tools/cli.clj b/src/cljam/tools/cli.clj
index a16672b..e4cb94a 100644
--- a/src/cljam/tools/cli.clj
+++ b/src/cljam/tools/cli.clj
@@ -90,12 +90,13 @@
   [["-t" "--thread THREAD" "Number of threads (0 is auto)"
     :default 0
     :parse-fn #(Integer/parseInt %)]
+   ["-i" "--index" "Create Index"]
    ["-h" "--help" "Print help"]])
 
 (defn- convert-usage [options-summary]
   (->> ["Convert file format based on the file extension."
         ""
-        "Usage: cljam convert [-t THREAD] <in-file> <out-file> [<out-file-2> [<out-file-3>]]"
+        "Usage: cljam convert [-t THREAD] [-i] <in-file> <out-file> [<out-file-2> [<out-file-3>]]"
         ""
         "Options:"
         options-summary]
@@ -108,18 +109,19 @@
       (not (<= 2 (count arguments) 4)) (exit 1 (convert-usage summary))
       errors (exit 1 (error-msg errors)))
     (let [[in & [out & more :as outs]] arguments]
-      (convert/convert in (if more outs out) :n-threads (:thread options))))
+      (convert/convert in (if more outs out) :n-threads (:thread options) :create-index? (:index options))))
   nil)
 
 ;; ### normalize command
 
 (def ^:private normalize-cli-options
-  [["-h" "--help"]])
+  [["-i" "--index" "Create Index"]
+   ["-h" "--help"]])
 
 (defn- normalize-usage [options-summary]
   (->> ["Normalize references of alignments"
         ""
-        "Usage: cljam normalize <in.bam|sam> <out.bam|sam>"
+        "Usage: cljam normalize [-i] <in.bam|sam> <out.bam|sam>"
         ""
         "Options:"
         options-summary]
@@ -133,7 +135,7 @@
       errors (exit 1 (error-msg errors)))
     (let [[in out] arguments]
       (with-open [r (sam/reader in)
-                  w (sam/writer out)]
+                  w (sam/writer out (:index options))]
         (normal/normalize r w))))
   nil)
 
@@ -145,12 +147,13 @@
    ["-c" "--chunk CHUNK" "Maximum number of alignments sorted per thread."
     :default sorter/default-chunk-size
     :parse-fn #(Integer/parseInt %)]
+   ["-i" "--index" "Create Index"]
    ["-h" "--help" "Print help"]])
 
 (defn- sort-usage [options-summary]
   (->> ["Sort alignments by leftmost coordinates."
         ""
-        "Usage: cljam sort [-o ORDER] [-c CHUNK] <in.bam|sam> <out.bam|sam>"
+        "Usage: cljam sort [-o ORDER] [-c CHUNK] [-i] <in.bam|sam> <out.bam|sam>"
         ""
         "Options:"
         options-summary]
@@ -164,7 +167,7 @@
       errors (exit 1 (error-msg errors)))
     (let [[in out] arguments]
       (with-open [r (sam/reader in)
-                  w (sam/writer out)]
+                  w (sam/writer out (:index options))]
         (condp = (:order options)
           (name header/order-coordinate) (sorter/sort-by-pos r w {:chunk-size (:chunk options)})
           (name header/order-queryname) (sorter/sort-by-qname r w {:chunk-size (:chunk options)})))))
@@ -298,12 +301,13 @@
 ;; ### level command
 
 (def ^:private level-cli-options
-  [["-h" "--help"]])
+  [["-i" "--index" "Create Index"]
+   ["-h" "--help"]])
 
 (defn- level-usage [options-summary]
   (->> ["Analyze a BAM file and add level information of alignments."
         ""
-        "Usage: cljam level <in.bam> <out.bam>"
+        "Usage: cljam level [-i] <in.bam> <out.bam>"
         ""
         "Options:"
         options-summary]
@@ -317,7 +321,7 @@
       errors (exit 1 (error-msg errors)))
     (let [[in out] arguments]
       (with-open [r (sam/reader in)
-                  w (sam/writer out)]
+                  w (sam/writer out (:index options))]
         (level/add-level r w))))
   nil)
 


Test patch:
diff --git a/test/cljam/io/bam_index/writer_test.clj b/test/cljam/io/bam_index/writer_test.clj
new file mode 100644
index 0000000..eef9c93
--- /dev/null
+++ b/test/cljam/io/bam_index/writer_test.clj
@@ -0,0 +1,110 @@
+(ns cljam.io.bam-index.writer-test
+  "Tests for cljam.io.bam-index."
+  (:require [clojure.test :refer [deftest is testing]]
+            [cljam.io.bam-index.writer :as writer]))
+
+(def update-last-pointer 29229056)
+(def input-index-1 {0 {:meta-data {:first-offset 97
+                                   :last-offset 555
+                                   :aligned-alns 6
+                                   :unaligned-alns 0}
+                       :bin-index {4681 [{:beg 97, :end 555}]}
+                       :linear-index {0 97}}
+                    1 {:meta-data {:first-offset 555
+                                   :last-offset 1031
+                                   :aligned-alns 6
+                                   :unaligned-alns 0}
+                       :bin-index {4681 [{:beg 555, :end 1031}]}
+                       :linear-index {0 555}}
+                    :no-coordinate-alns 0})
+(def output-index-1 {0 {:meta-data {:first-offset 97
+                                    :last-offset 555
+                                    :aligned-alns 6
+                                    :unaligned-alns 0}
+                        :bin-index {4681 [{:beg 97, :end 555}]}
+                        :linear-index {0 97}}
+                     1 {:meta-data {:first-offset 555
+                                    :last-offset 29229056
+                                    :aligned-alns 6
+                                    :unaligned-alns 0}
+                        :bin-index {4681 [{:beg 555, :end 29229056}]}
+                        :linear-index {0 555}}
+                     :no-coordinate-alns 0})
+(def input-index-2 {0 {:meta-data {:first-offset 97
+                                   :last-offset 555
+                                   :aligned-alns 6
+                                   :unaligned-alns 0}
+                       :bin-index {4681 [{:beg 97, :end 345}]
+                                   4781 [{:beg 345, :end 555}]}
+                       :linear-index {0 97}}
+                    1 {:meta-data {:first-offset 555
+                                   :last-offset 1031
+                                   :aligned-alns 6
+                                   :unaligned-alns 0}
+                       :bin-index {4881 [{:beg 555, :end 800}
+                                         {:beg 900, :end 1031}]}
+                       :linear-index {0 555}}
+                    2 {:meta-data {:first-offset 1031
+                                   :last-offset 2031
+                                   :aligned-alns 6
+                                   :unaligned-alns 0}
+                       :bin-index {4981 [{:beg 1031, :end 1131}
+                                         {:beg 1231, :end 1331}]
+                                   5081 [{:beg 1331, :end 1431}
+                                         {:beg 1531, :end 2031}]}
+                       :linear-index {0 1031}}
+                    :no-coordinate-alns 0})
+(def output-index-2 {0 {:meta-data {:first-offset 97
+                                    :last-offset 555
+                                    :aligned-alns 6
+                                    :unaligned-alns 0}
+                        :bin-index {4681 [{:beg 97, :end 345}]
+                                    4781 [{:beg 345, :end 555}]}
+                        :linear-index {0 97}}
+                     1 {:meta-data {:first-offset 555
+                                    :last-offset 1031
+                                    :aligned-alns 6
+                                    :unaligned-alns 0}
+                        :bin-index {4881 [{:beg 555, :end 800}
+                                          {:beg 900, :end 1031}]}
+                        :linear-index {0 555}}
+                     2 {:meta-data {:first-offset 1031
+                                    :last-offset 29229056
+                                    :aligned-alns 6
+                                    :unaligned-alns 0}
+                        :bin-index {4981 [{:beg 1031, :end 1131}
+                                          {:beg 1231, :end 1331}]
+                                    5081 [{:beg 1331, :end 1431}
+                                          {:beg 1531, :end 29229056}]}
+                        :linear-index {0 1031}}
+                     :no-coordinate-alns 0})
+(def input-index-not-update-1 {:no-coordinate-alns 0})
+(def input-index-not-update-2 {0 {:meta-data {:first-offset 97
+                                              :last-offset 555
+                                              :aligned-alns 6
+                                              :unaligned-alns 0}
+                                  :bin-index {4681 [{:beg 97, :end 555}]}
+                                  :linear-index {0 97}}
+                               1 {:meta-data {:first-offset 555
+                                              :last-offset 1031
+                                              :aligned-alns 6
+                                              :unaligned-alns 0}
+                                  :bin-index {4681 [{:beg 555, :end 1031}]}
+                                  :linear-index {0 555}}
+                               :no-coordinate-alns 3})
+
+;;; unit test
+
+;;; update-last-pointer
+
+(deftest update-last-pointer-test
+  (testing "update"
+    (let [output-1 (writer/update-last-pointer input-index-1 update-last-pointer)
+          output-2 (writer/update-last-pointer input-index-2 update-last-pointer)]
+      (is (= output-1 output-index-1))
+      (is (= output-2 output-index-2))))
+  (testing "not update"
+    (let [output-1 (writer/update-last-pointer input-index-not-update-1 update-last-pointer)
+          output-2 (writer/update-last-pointer input-index-not-update-2 update-last-pointer)]
+      (is (= output-1 input-index-not-update-1))
+      (is (= output-2 input-index-not-update-2)))))
diff --git a/test/cljam/io/sam_test.clj b/test/cljam/io/sam_test.clj
index c7195c1..ca39da1 100644
--- a/test/cljam/io/sam_test.clj
+++ b/test/cljam/io/sam_test.clj
@@ -23,6 +23,7 @@
               test-sam-data
               test-sam-refs
               test-sam-sorted-by-pos
+              test-sam-sorted-by-qname
               test-sorted-bam-file
               test-sorted-bam-data
               small-bam-file
@@ -41,6 +42,7 @@
 
 (def temp-sam-file (str temp-dir "/test.sam"))
 (def temp-bam-file (str temp-dir "/test.bam"))
+(def temp-bam-file-2 (str temp-dir "/test2.bam"))
 (def temp-bam-file-sorted (str temp-dir "/test.sorted.bam"))
 (def temp-small-bam-file (str temp-dir "/small.bam"))
 (def temp-medium-bam-file (str temp-dir "/medium.bam"))
@@ -309,6 +311,52 @@
         "temp.baam"
         "temp.bai"))))
 
+(deftest writer-index-option-test
+  (testing "sam"
+    (with-before-after {:before (prepare-cache!)
+                        :after (clean-cache!)}
+      (with-open [w (sam/writer temp-sam-file false)]
+        (sam/write-header w (:header test-sam-sorted-by-pos))
+        (sam/write-refs w (:header test-sam-sorted-by-pos))
+        (sam/write-alignments w (:alignments test-sam-sorted-by-pos) (:header test-sam-sorted-by-pos)))
+      (is (not (.exists (cio/file (str temp-sam-file ".bai")))))))
+  (testing "bam"
+    (with-before-after {:before (prepare-cache!)
+                        :after (clean-cache!)}
+      (with-open [w (sam/writer temp-bam-file false)]
+        (sam/write-header w (:header test-sam-sorted-by-pos))
+        (sam/write-refs w (:header test-sam-sorted-by-pos))
+        (sam/write-alignments w (:alignments test-sam-sorted-by-pos) (:header test-sam-sorted-by-pos)))
+      (is (not (.exists (cio/file (str temp-bam-file ".bai")))))
+      (with-open [w (sam/writer temp-bam-file true)]
+        (sam/write-header w (:header test-sam))
+        (sam/write-refs w (:header test-sam))
+        (sam/write-alignments w (:alignments test-sam) (:header test-sam)))
+      (is (not (.exists (cio/file (str temp-bam-file ".bai")))))
+      (with-open [w (sam/writer temp-bam-file true)]
+        (sam/write-header w (:header test-sam-sorted-by-qname))
+        (sam/write-refs w (:header test-sam-sorted-by-qname))
+        (sam/write-alignments w (:alignments test-sam-sorted-by-qname) (:header test-sam-sorted-by-qname)))
+      (is (not (.exists (cio/file (str temp-bam-file ".bai")))))
+      (with-open [r (sam/reader test-sorted-bam-file)
+                  w (sam/writer temp-bam-file true)]
+        (sam/write-header w (sam/read-header r))
+        (sam/write-refs w (sam/read-header r))
+        (sam/write-alignments w (sam/read-alignments r) (sam/read-header r)))
+      (is (.exists (cio/file (str temp-bam-file ".bai"))))
+      (is (same-file? test-bai-file (str temp-bam-file ".bai")))
+      (with-open [r (sam/reader test-sorted-bam-file)
+                  w (sam/writer temp-bam-file-2 true)]
+        (sam/write-header w (sam/read-header r))
+        (sam/write-refs w (sam/read-header r))
+        (sam/write-blocks w (sam/read-blocks r)))
+      (is (.exists (cio/file (str temp-bam-file-2 ".bai"))))
+      (is (same-file? test-bai-file (str temp-bam-file-2 ".bai")))))
+  (testing "throws Exception"
+    (with-before-after {:before (prepare-cache!)
+                        :after (clean-cache!)}
+      (is (thrown? Exception (sam/writer temp-sam-file true))))))
+
 (def test-options
   [{:Xa {:type "A", :value \p}}
    {:Xi {:type "i", :value -100}}
diff --git a/test/cljam/io/util/bin_test.clj b/test/cljam/io/util/bin_test.clj
index b751e15..077c989 100644
--- a/test/cljam/io/util/bin_test.clj
+++ b/test/cljam/io/util/bin_test.clj
@@ -8,67 +8,41 @@
             [cljam.io.csi :as csi]
             [cljam.io.util.bin :as util-bin]))
 
-(deftest max-pos-test
-  (are [?min-shift ?depth ?max-pos]
-       (= ?max-pos (util-bin/max-pos ?min-shift ?depth))
-    14 5 536870912
-    15 5 1073741824
-    0 0 1
-    0 1 8
-    0 2 64
-    1 1 16
-    1 2 128))
-
-(deftest first-bin-of-level-test
-  (are [?level ?first-bin]
-       (= ?first-bin (util-bin/first-bin-of-level ?level))
-    0 0
-    1 1
-    2 (+ 1 8)
-    3 (+ 1 8 64)
-    4 (+ 1 8 64 512)
-    5 (+ 1 8 64 512 4096)
-    6 (+ 1 8 64 512 4096 32768)))
-
-(deftest bin-width-of-level-test
-  (are [?level ?min-shift ?depth ?bin-width]
-       (= ?bin-width (util-bin/bin-width-of-level ?level ?min-shift ?depth))
-    0 14 5 536870912
-    1 14 5 67108864
-    2 14 5 8388608
-    3 14 5 1048576
-    4 14 5 131072
-    5 14 5 16384
-    0 15 5 1073741824
-    0 0 0 1
-    0 0 1 8
-    1 0 1 1
-    1 0 2 8))
+(deftest get-spans-returns-a-sequence-including-regions
+  (let [tabix* (tabix/read-index test-tabix-file)]
+    (is (= [[0 50872]] (util-bin/get-spans tabix* 0 1 100)))
+    (is (every? #(and (= 2 (count %))
+                      (number? (first %))
+                      (number? (second %)))
+                (util-bin/get-spans tabix* 0 1 100))))
+  (let [csi* (csi/read-index test-csi-file)]
+    (is (= [[3904 3973]] (util-bin/get-spans csi* 0 1 100000)))
+    (is (every? #(and (= 2 (count %))
+                      (number? (first %))
+                      (number? (second %)))
+                (util-bin/get-spans csi* 0 1 100000)))))
 
-(deftest bin-level-test
-  (are [?bin ?level]
-       (= ?level (util-bin/bin-level ?bin))
-    0 0
-    1 1
-    2 1
-    8 1
-    9 2
-    72 2
-    73 3
-    584 3
-    585 4
-    4680 4
-    4681 5
-    37448 5
-    37449 6))
+(deftest reg->bins-test
+  (are [x] (= [0 1 9 73 585 4681]
+              (#'util-bin/reg->bins 1 (bit-shift-left 1 x) x 5))
+    13 14 15 16)
+  (are [x] (= [0 1 9 73 585 4681 4682]
+              (#'util-bin/reg->bins 1 (inc (bit-shift-left 1 x)) x 5))
+    13 14 15 16)
+  (is (= [0 2 17]
+         (#'util-bin/reg->bins 9 9 0 2)))
+  (is (= [0 1 9 73 585]
+         (#'util-bin/reg->bins 1 1 14 4)))
+  (is (= [0 1 9 73 585 4681]
+         (#'util-bin/reg->bins 1 1 14 5)))
+  (is (= [0 1 9 73 585 4681 37449]
+         (#'util-bin/reg->bins 1 1 14 6)))
+  (is (= [0 1 9 73 585 4681 37449 299593]
+         (#'util-bin/reg->bins 1 1 14 7)))
+  (is (= [0 1 9 73 585 4681 37449 37450]
+         (#'util-bin/reg->bins 1 32769 15 6))))
 
 (deftest bin-beg-test
-  (are [?bin ?min-shift ?depth ?bin-beg]
-       (= ?bin-beg (util-bin/bin-beg ?bin ?min-shift ?depth))
-    4680 14 5 536739841
-    4681 14 5 1
-    4682 14 5 16385
-    4683 14 5 32769)
   (doseq [index-shift [12 14 16]]
     (let [min-size (bit-shift-left 1 index-shift)]
       (is (= 1 (util-bin/bin-beg 4681 index-shift 5)))
@@ -80,56 +54,16 @@
       (is (= (+ 1 (* min-size 24)) (util-bin/bin-beg 588 index-shift 5))))))
 
 (deftest max-bin-test
-  (are [?depth ?ans] (= (util-bin/max-bin ?depth) ?ans)
+  (are [depth ans] (= (util-bin/max-bin depth) ans)
     1 8
     2 72
     3 584
     5 37448))
 
-(deftest leading-bins-at-level-test
-  (are [?pos ?level ?min-shift ?depth ?bins]
-       (= ?bins (util-bin/leading-bins-at-level ?pos ?level ?min-shift ?depth))
-    1 0 14 5 0
-    1 5 14 5 0
-    16385 5 14 5 1
-    536870912 5 14 5 32768))
-
-(deftest pos->lidx-offset-test
-  (are [?pos ?linear-index-shift ?offset]
-       (= ?offset (util-bin/pos->lidx-offset ?pos ?linear-index-shift))
-    0 14 0
-    1 14 0
-    16384 14 0
-    16385 14 1
-    32768 14 1
-    32769 14 2))
-
-(deftest reg->bins-test
-  (are [x] (= [0 1 9 73 585 4681]
-              (util-bin/reg->bins 1 (bit-shift-left 1 x) x 5))
-    13 14 15 16)
-  (are [x] (= [0 1 9 73 585 4681 4682]
-              (util-bin/reg->bins 1 (inc (bit-shift-left 1 x)) x 5))
-    13 14 15 16)
-  (is (= [0 2 17]
-         (util-bin/reg->bins 9 9 0 2)))
-  (is (= [0 1 9 73 585]
-         (util-bin/reg->bins 1 1 14 4)))
-  (is (= [0 1 9 73 585 4681]
-         (util-bin/reg->bins 1 1 14 5)))
-  (is (= [0 1 9 73 585 4681 37449]
-         (util-bin/reg->bins 1 1 14 6)))
-  (is (= [0 1 9 73 585 4681 37449 299593]
-         (util-bin/reg->bins 1 1 14 7)))
-  (is (= [0 1 9 73 585 4681 37449 37450]
-         (util-bin/reg->bins 1 32769 15 6))))
-
 (deftest reg->bin-test
   (testing "BAI-compatible"
     (are [?start ?end ?bin]
-         (= ?bin
-            (util-bin/reg->bin ?start ?end 14 5)
-            ((set (util-bin/reg->bins ?start ?end 14 5)) ?bin))
+         (= ?bin (util-bin/reg->bin ?start ?end 14 5))
       0 0 4681
       0 1 4681
       1 1 4681
@@ -145,9 +79,7 @@
       536870912 536870913 37448))
   (testing "1-by-1"
     (are [?start ?end ?bin]
-         (= ?bin
-            (util-bin/reg->bin ?start ?end 0 2)
-            ((set (util-bin/reg->bins ?start ?end 0 2)) ?bin))
+         (= ?bin (util-bin/reg->bin ?start ?end 0 2))
       0 1 9
       1 1 9
       2 2 10
@@ -164,17 +96,3 @@
     (is (= (util-bin/reg->bin 1 1 14 7) 299593))
     (is (= (util-bin/reg->bin 1 32769 15 6) 4681))
     (is (= (util-bin/reg->bin 240877561 240877568 14 6) 52150))))
-
-(deftest get-spans-returns-a-sequence-including-regions
-  (let [tabix* (tabix/read-index test-tabix-file)]
-    (is (= [[0 50872]] (util-bin/get-spans tabix* 0 1 100)))
-    (is (every? #(and (= 2 (count %))
-                      (number? (first %))
-                      (number? (second %)))
-                (util-bin/get-spans tabix* 0 1 100))))
-  (let [csi* (csi/read-index test-csi-file)]
-    (is (= [[3904 3973]] (util-bin/get-spans csi* 0 1 100000)))
-    (is (every? #(and (= 2 (count %))
-                      (number? (first %))
-                      (number? (second %)))
-                (util-bin/get-spans csi* 0 1 100000)))))