htslib 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -49
  3. data/TUTORIAL.md +9 -20
  4. data/ext/htslib_native/extconf.rb +41 -0
  5. data/ext/htslib_native/htslib_native.h +11 -0
  6. data/ext/htslib_native/htslib_native_ext.c +48 -0
  7. data/ext/htslib_native/native_bam.c +1108 -0
  8. data/ext/htslib_native/native_bcf.c +369 -0
  9. data/ext/htslib_native/native_faidx.c +155 -0
  10. data/ext/htslib_native/native_tabix.c +246 -0
  11. data/lib/hts/bam/auxi.rb +87 -342
  12. data/lib/hts/bam/base_mod.rb +37 -73
  13. data/lib/hts/bam/cigar.rb +9 -55
  14. data/lib/hts/bam/flag.rb +19 -27
  15. data/lib/hts/bam/header.rb +29 -56
  16. data/lib/hts/bam/mpileup.rb +158 -132
  17. data/lib/hts/bam/pileup.rb +120 -145
  18. data/lib/hts/bam/record.rb +233 -270
  19. data/lib/hts/bam.rb +102 -42
  20. data/lib/hts/bcf/errors.rb +1 -0
  21. data/lib/hts/bcf/format.rb +278 -379
  22. data/lib/hts/bcf/header.rb +38 -123
  23. data/lib/hts/bcf/header_record.rb +9 -35
  24. data/lib/hts/bcf/info.rb +72 -320
  25. data/lib/hts/bcf/record.rb +61 -102
  26. data/lib/hts/bcf.rb +149 -323
  27. data/lib/hts/faidx.rb +28 -87
  28. data/lib/hts/hts.rb +6 -94
  29. data/lib/hts/native.rb +13 -0
  30. data/lib/hts/tabix.rb +93 -48
  31. data/lib/hts/version.rb +1 -1
  32. data/lib/htslib.rb +6 -52
  33. metadata +13 -64
  34. data/lib/hts/ffi_ext/README.md +0 -8
  35. data/lib/hts/ffi_ext/pointer.rb +0 -18
  36. data/lib/hts/ffi_ext/struct.rb +0 -45
  37. data/lib/hts/libhts/bgzf.rb +0 -199
  38. data/lib/hts/libhts/constants.rb +0 -658
  39. data/lib/hts/libhts/cram.rb +0 -471
  40. data/lib/hts/libhts/fai.rb +0 -146
  41. data/lib/hts/libhts/hfile.rb +0 -109
  42. data/lib/hts/libhts/hts.rb +0 -477
  43. data/lib/hts/libhts/kfunc.rb +0 -38
  44. data/lib/hts/libhts/sam.rb +0 -760
  45. data/lib/hts/libhts/sam_funcs.rb +0 -155
  46. data/lib/hts/libhts/tbx.rb +0 -79
  47. data/lib/hts/libhts/tbx_funcs.rb +0 -32
  48. data/lib/hts/libhts/thread_pool.rb +0 -139
  49. data/lib/hts/libhts/vcf.rb +0 -557
  50. data/lib/hts/libhts/vcf_funcs.rb +0 -353
  51. data/lib/hts/libhts.rb +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86838244ad390e04fb293d3db9fd639c837dcd7b2309cc0c83da0b5925e7185c
4
- data.tar.gz: 547063279b934706efcf919d287217f4231fe76d2e8fb3ae6df8e4dd7b6040ad
3
+ metadata.gz: 44b02c34b18d412043b9063ef1d8da5f9f9f88bd4bdfcf7c8537e0a1dbcebe14
4
+ data.tar.gz: cc76ea23d6fa215755a9c0ccaab3de11e5e2465b877e3481565b9586d9a730d8
5
5
  SHA512:
6
- metadata.gz: 787f9167abef88ddbd758d3155f1f3568bb99d8a5a340086a1cc677be46e0e77ec5a0ef56d201610f06143d0f54a20a1f2563ae3e080996564b5b5545e3e8a2a
7
- data.tar.gz: d07ae5e437c9729a39e32adb12ff694560eaa33e44094679199bc8b742ccaab4c5a111c7e6391a314a8b6a19fae82acea549123b57497690678e1b2b1746f2c7
6
+ metadata.gz: 726b1885741d8091cac5810a1522523cdc2dda959f244fe00a34a02a12ef73b6cfa5b1f5982538a4ca18bfb0b94e5d9e3ec30699ab2f4cd33f6206e68d01061b
7
+ data.tar.gz: 47055c773ce723e100e775528d77db8e6755f2f0b167f0d20037c461009453f3979260f010bb54a9e177a1a51ffa624dea7bda3d9ad04f8efd441b5d13d58c07
data/README.md CHANGED
@@ -16,11 +16,10 @@ Ruby-htslib is the [Ruby](https://www.ruby-lang.org) bindings to [HTSlib](https:
16
16
  ## Requirements
17
17
 
18
18
  - [Ruby](https://github.com/ruby/ruby) 3.1 or above.
19
- - [HTSlib](https://github.com/samtools/htslib)
19
+ - [HTSlib](https://github.com/samtools/htslib), including headers and `pkg-config` metadata
20
20
  - Ubuntu : `apt install libhts-dev`
21
21
  - macOS : `brew install htslib`
22
22
  - Windows : [mingw-w64-htslib](https://packages.msys2.org/base/mingw-w64-htslib) is automatically fetched when installing the gem ([RubyInstaller](https://rubyinstaller.org) only).
23
- - Build from source code (see the Development section)
24
23
 
25
24
  ## Installation
26
25
 
@@ -28,15 +27,15 @@ Ruby-htslib is the [Ruby](https://www.ruby-lang.org) bindings to [HTSlib](https:
28
27
  gem install htslib
29
28
  ```
30
29
 
31
- If you have installed htslib with apt on Ubuntu or homebrew on Mac, [pkg-config](https://github.com/ruby-gnome/pkg-config)
32
- will automatically detect the location of the shared library. If pkg-config does not work well, set `PKG_CONFIG_PATH`.
33
- Alternatively, you can specify the directory of the shared library by setting the environment variable `HTSLIBDIR`.
30
+ The gem builds a native extension and links to the system HTSlib. `pkg-config`
31
+ normally finds it automatically. For a non-standard installation, set
32
+ `PKG_CONFIG_PATH`, pass `--with-htslib-dir`, or set `HTSLIBDIR` while installing.
34
33
 
35
34
  ```sh
36
- export HTSLIBDIR="/your/path/to/htslib" # Directory where libhts.so is located
35
+ gem install htslib -- --with-htslib-dir=/your/htslib/prefix
37
36
  ```
38
37
 
39
- ruby-htslib also works on Windows. If you use RubyInstaller, htslib will be prepared automatically.
38
+ RubyInstaller installs the declared MSYS2 HTSlib dependency on Windows.
40
39
 
41
40
  ## Usage
42
41
 
@@ -77,6 +76,41 @@ HTS::Bam.open("test/fixtures/moo.bam") do |bam|
77
76
  end
78
77
  ```
79
78
 
79
+ Creating a BAM file from Ruby values
80
+
81
+ ```ruby
82
+ header = HTS::Bam::Header.new
83
+ header.update_hd(version: "1.6", sort_order: "unsorted")
84
+ header.add_sq("chr1", length: 1_000_000)
85
+
86
+ record = HTS::Bam::Record.new(
87
+ header,
88
+ qname: "read1",
89
+ chrom: "chr1", # resolved through the header
90
+ pos: 99, # zero-based
91
+ mapq: 60,
92
+ cigar: "4M",
93
+ sequence: "ACGT",
94
+ qualities: [30, 31, 32, 33],
95
+ aux: { "NM" => 0 }
96
+ )
97
+
98
+ HTS::Bam.open("output.bam", "wb") do |bam|
99
+ bam.write_header(header)
100
+ bam << record
101
+ end
102
+ ```
103
+
104
+ Use `quality_string:` for a Phred+33 string instead of numeric `qualities:`.
105
+ Omit qualities (or pass `nil`) to write missing qualities. `seq:` / `qual:`
106
+ are accepted as concise aliases. Reference names can also be assigned with
107
+ `record.chrom=` and `record.mate_chrom=`.
108
+
109
+ BAM/CRAM, BCF/VCF, and Tabix indexes are loaded lazily when an indexed
110
+ operation such as `query` or a region-limited pileup first needs them. Pass an
111
+ explicit `index:` path to `open` to validate and load that index immediately.
112
+ `index_loaded?` reports whether the index is currently loaded.
113
+
80
114
  ### HTS::Bcf - VCF / BCF - Variant Call Format file
81
115
 
82
116
  Reading fields
@@ -129,29 +163,48 @@ end
129
163
  tb.close
130
164
  ```
131
165
 
132
- ### Low-level API
166
+ ### Low-allocation traversal
133
167
 
134
- Middle architectural layer between high-level Ruby code and low-level C code.
135
- `HTS::LibHTS` provides native C functions using [Ruby-FFI](https://github.com/ffi/ffi).
168
+ Large scans can avoid per-sample arrays and genotype strings:
136
169
 
137
170
  ```ruby
138
- require 'htslib'
139
-
140
- a = HTS::LibHTS.hts_open("a.bam", "r")
141
- b = HTS::LibHTS.hts_get_format(a)
142
- p b[:category]
143
- p b[:format]
171
+ HTS::Bcf.open("variants.bcf", samples: %w[S1 S2], unpack: :format) do |bcf|
172
+ bcf.each do |record|
173
+ record.format.each_genotype do |sample_index, genotype|
174
+ genotype.each_allele do |allele, phased, missing|
175
+ # Consume the primitive values here.
176
+ end
177
+ end
178
+
179
+ record.format.each_i32("DP") { |sample_index, depth| }
180
+ record.format.each_i32_vector("AD") { |sample_index, values| values.each { |depth| } }
181
+ record.format.each_f32_vector("GL") { |sample_index, values| values.each { |likelihood| } }
182
+ end
183
+ end
144
184
  ```
145
185
 
146
- The low-level API makes it possible to perform detailed operations, such as calling CRAM-specific functions.
186
+ The genotype and numeric vector objects yielded by these iterators are borrowed
187
+ and reused. Consume them inside the block, or call `to_s` / `to_a` to retain an
188
+ owning value. Reading a different FORMAT key does not invalidate a view; reading
189
+ the same key again makes an old view raise `InvalidBorrowedViewError` instead of
190
+ silently returning overwritten data. `genotype_strings`, `get`, and `[]` remain
191
+ the allocating convenience APIs. Use `unpack: :site_only` when FORMAT columns
192
+ are not needed.
147
193
 
148
- #### Macro functions
194
+ Tabix likewise separates raw and materialized access:
149
195
 
150
- HTSlib is designed to improve performance with many macro functions. However, it is not possible to call C macro functions directly from Ruby-FFI. To overcome this, important macro functions have been re-implemented in Ruby, allowing them to be called in the same way as native functions.
196
+ ```ruby
197
+ tb.each_line("chr1:1-1000") { |line| }
198
+ tb.each_fields("chr1:1-1000") { |fields| }
199
+ tb.each_selected_fields("chr1:1-1000", 0, 3, 4) { |values| }
200
+ ```
151
201
 
152
- #### Garbage Collection and Memory Freeing
202
+ ### Native backend migration
153
203
 
154
- A small number of commonly used structs, such as `Bam1` and `Bcf1`, are implemented using FFI's `ManagedStruct`. This allows for automatic memory release when Ruby's garbage collection is triggered. On the other hand, other structs are implemented using `FFI::Struct`, and they will require manual memory release.
204
+ Version 0.5 uses a compiled extension for every supported high-level API. The
205
+ former low-level binding, pointer, and runtime library-switching APIs were
206
+ implementation details and have been removed. Native handles are not exposed;
207
+ install failures report the missing HTSlib build requirement.
155
208
 
156
209
  ### Need more speed?
157
210
 
@@ -165,22 +218,19 @@ Try Crystal. [HTS.cr](https://github.com/bio-cr/hts.cr) is implemented in Crysta
165
218
 
166
219
  ## Development
167
220
 
168
- #### Compile from source code
221
+ #### Build the extension
169
222
 
170
- [GNU Autotools](https://en.wikipedia.org/wiki/GNU_Autotools) is required to compile htslib.
171
- To get started with development:
223
+ Install the system HTSlib development package first, then:
172
224
 
173
225
  ```sh
174
- git clone --recursive https://github.com/kojix2/ruby-htslib
226
+ git clone https://github.com/kojix2/ruby-htslib
175
227
  cd ruby-htslib
176
228
  bundle install
177
- bundle exec rake htslib:build
178
229
  bundle exec rake test
179
230
  ```
180
231
 
181
- #### Macro functions are reimplemented
182
-
183
- HTSlib has many macro functions. These macro functions cannot be called from FFI and must be reimplemented in Ruby.
232
+ Use `bundle exec rake test:local` for fixture-only tests and
233
+ `bundle exec rake test:remote` for the network-dependent URI suite.
184
234
 
185
235
  #### Use the latest Ruby
186
236
 
@@ -196,30 +246,10 @@ Return value
196
246
 
197
247
  The most challenging part is the return value. In the Crystal language, methods are expected to return only one type. On the other hand, in the Ruby language, methods that return multiple classes are very common. For example, in the Crystal language, the compiler gets confused if the return value is one of six types: Int32, Int64, Float32, Float64, Nil, or String. In fact Crystal allows you to do that. But the code gets a little messy. In Ruby, this is very common and doesn't cause any problems.
198
248
 
199
- Memory management
200
-
201
- Ruby and Crystal are languages that use garbage collection. However, the memory release policy for allocated C structures is slightly different: in Ruby-FFI, you can define a `self.release` method in `FFI::Struct`. This method is called when GC. So you don't have to worry about memory in high-level APIs like Bam::Record or Bcf::Record, etc. Crystal requires you to define a finalize method on each class. So you need to define it in Bam::Record or Bcf::Record.
202
-
203
- Macro functions
204
-
205
- In ruby-htslib, C macro functions are added to `LibHTS`, but in Crystal, `LibHTS` is a Lib, so methods cannot be added. methods are added to `LibHTS2`.
206
-
207
249
  #### Naming convention
208
250
 
209
251
  If you are not sure about the naming of a method, follow the Rust-htslib API. This is a very weak rule. if a more appropriate name is found later in Ruby, it will replace it.
210
252
 
211
- #### Support for bitfields of structures
212
-
213
- Since Ruby-FFI does not support structure bit fields, the following extensions are used.
214
-
215
- - [ffi-bitfield](https://github.com/kojix2/ffi-bitfield) - Extension of Ruby-FFI to support bitfields.
216
-
217
- #### Automatic validation
218
-
219
- In the `script` directory, there are several tools to help implement ruby-htslib. Scripts using c2ffi can check the coverage of htslib functions in Ruby-htslib. They are useful when new versions of htslib are released.
220
-
221
- - [c2ffi](https://github.com/rpav/c2ffi) is a tool to create JSON format metadata from C header files.
222
-
223
253
  ## Contributing
224
254
 
225
255
  Ruby-htslib is a library under development, so even minor improvements like typo fixes are welcome! Please feel free to send us your pull requests.
data/TUTORIAL.md CHANGED
@@ -17,33 +17,30 @@ Bcf o-- `Bcf::Record`
17
17
  `Bcf::Record` o-- Info
18
18
  `Bcf::Record` o-- Format
19
19
  class Bam{
20
- +@hts_file : FFI::Struct
20
+ -@native
21
21
  +@header : Bam::Header
22
22
  +@file_name
23
23
  +@index_name
24
24
  +@mode
25
- +struct()
26
25
  +build_index()
27
26
  +each() Enumerable
28
27
  +query()
29
28
  }
30
29
  class Bcf{
31
- +@hts_file : FFI::Struct
30
+ -@native
32
31
  +@header : Bcf::Header
33
32
  +@file_name
34
33
  +@index_name
35
34
  +@mode
36
- +struct()
37
35
  +build_index()
38
36
  +each() Enumerable
39
37
  +query()
40
38
  }
41
39
  class Tabix~Hts~{
42
- +@hts_file : FFI::Struct
40
+ -@native
43
41
  }
44
42
  class `Bam::Header`{
45
- +@sam_hdr : FFI::Struct
46
- +struct()
43
+ -@native
47
44
  +target_count()
48
45
  +target_names()
49
46
  +name2tid()
@@ -51,9 +48,8 @@ class `Bam::Header`{
51
48
  +to_s()
52
49
  }
53
50
  class `Bam::Record` {
54
- +@bam1 : FFI::Struct
51
+ -@native
55
52
  +@header : Bam::Header
56
- +struct()
57
53
  +qname()
58
54
  +qname=()
59
55
  +tid()
@@ -97,14 +93,12 @@ class `Aux` {
97
93
  +get_string()
98
94
  }
99
95
  class `Bcf::Header`{
100
- +@bcf_hdr : FFI::Struct
101
- +struct()
96
+ -@native
102
97
  +to_s()
103
98
  }
104
99
  class `Bcf::Record`{
105
- +@bcf1 : FFI::Struct
100
+ -@native
106
101
  +@header : Bcf::Header
107
- +struct()
108
102
  +rid()
109
103
  +rid=()
110
104
  +chrom()
@@ -191,13 +185,8 @@ class Faidx{
191
185
  gem install htslib
192
186
  ```
193
187
 
194
- You can check which shared libraries are used by ruby-htslib as follows
195
-
196
- ```ruby
197
- require "htslib"
198
- puts HTS.lib_path
199
- # => "/home/kojix2/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/htslib-0.2.6/vendor/libhts.so"
200
- ```
188
+ The gem compiles its native extension against the system HTSlib. Install the
189
+ HTSlib development package before installing the gem.
201
190
 
202
191
  ## HTS::Bam - SAM / BAM / CRAM - Sequence Alignment Map file
203
192
 
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ htslib_dir = with_config("htslib-dir", ENV["HTSLIBDIR"])
6
+ if htslib_dir
7
+ include_dir = File.directory?(File.join(htslib_dir, "include")) ? File.join(htslib_dir, "include") : htslib_dir
8
+ library_dir = if File.directory?(File.join(htslib_dir, "lib"))
9
+ File.join(htslib_dir, "lib")
10
+ else
11
+ htslib_dir
12
+ end
13
+ dir_config("htslib", include_dir, library_dir)
14
+ end
15
+
16
+ pkg_config("htslib") unless htslib_dir
17
+
18
+ abort "HTSlib headers were not found. Install the HTSlib development package or set --with-htslib-dir." unless \
19
+ have_header("htslib/hts.h") && have_header("htslib/sam.h") &&
20
+ have_header("htslib/vcf.h") && have_header("htslib/faidx.h") && have_header("htslib/tbx.h")
21
+
22
+ abort "HTSlib library was not found. Install HTSlib or set --with-htslib-dir." unless \
23
+ have_library("hts", "hts_version")
24
+
25
+ required_functions = {
26
+ "sam_read1" => "htslib/sam.h",
27
+ "bam_plp_init" => "htslib/sam.h",
28
+ "bam_mplp64_auto" => "htslib/sam.h",
29
+ "bam_mplp_init_overlaps" => "htslib/sam.h",
30
+ "hts_base_mod_state_alloc" => "htslib/sam.h",
31
+ "bcf_read" => "htslib/vcf.h",
32
+ "bcf_hdr_set" => "htslib/vcf.h",
33
+ "fai_load3_format" => "htslib/faidx.h",
34
+ "tbx_index_load3" => "htslib/tbx.h"
35
+ }
36
+ missing_functions = required_functions.reject { |function, header| have_func(function, header) }.keys
37
+ unless missing_functions.empty?
38
+ abort "HTSlib is missing required functions: #{missing_functions.join(', ')}. Install a supported HTSlib development package."
39
+ end
40
+
41
+ create_makefile("htslib_native_ext")
@@ -0,0 +1,11 @@
1
+ #ifndef RUBY_HTSLIB_NATIVE_H
2
+ #define RUBY_HTSLIB_NATIVE_H
3
+
4
+ #include "ruby.h"
5
+
6
+ void Init_htslib_native_faidx(VALUE native);
7
+ void Init_htslib_native_tabix(VALUE native);
8
+ void Init_htslib_native_bam(VALUE native);
9
+ void Init_htslib_native_bcf(VALUE native);
10
+
11
+ #endif
@@ -0,0 +1,48 @@
1
+ #include "htslib_native.h"
2
+ #include <htslib/hts.h>
3
+
4
+ static VALUE native_htslib_version(VALUE self)
5
+ {
6
+ return rb_str_new_cstr(hts_version());
7
+ }
8
+
9
+ static VALUE selected_fields(VALUE self, VALUE line, VALUE columns)
10
+ {
11
+ const char *bytes;
12
+ long length, column_count, field_start = 0, column = 0, offset, i;
13
+ VALUE result;
14
+
15
+ StringValue(line);
16
+ Check_Type(columns, T_ARRAY);
17
+ bytes = RSTRING_PTR(line);
18
+ length = RSTRING_LEN(line);
19
+ column_count = RARRAY_LEN(columns);
20
+ result = rb_ary_new_capa(column_count);
21
+ for (i = 0; i < column_count; i++) rb_ary_push(result, Qnil);
22
+
23
+ for (offset = 0; offset <= length; offset++) {
24
+ if (offset == length || bytes[offset] == '\t') {
25
+ for (i = 0; i < column_count; i++) {
26
+ long requested = NUM2LONG(rb_ary_entry(columns, i));
27
+ if (requested == column) {
28
+ rb_ary_store(result, i, rb_str_new(bytes + field_start, offset - field_start));
29
+ }
30
+ }
31
+ column++;
32
+ field_start = offset + 1;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+
38
+ void Init_htslib_native_ext(void)
39
+ {
40
+ VALUE hts = rb_define_module("HTS");
41
+ VALUE native = rb_define_module_under(hts, "Native");
42
+ rb_define_module_function(native, "htslib_version", native_htslib_version, 0);
43
+ rb_define_module_function(native, "selected_fields", selected_fields, 2);
44
+ Init_htslib_native_faidx(native);
45
+ Init_htslib_native_tabix(native);
46
+ Init_htslib_native_bam(native);
47
+ Init_htslib_native_bcf(native);
48
+ }