ruby-igv 0.0.6 → 0.0.7

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -12
  3. data/lib/igv/version.rb +1 -1
  4. data/lib/igv.rb +242 -53
  5. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0cb49945b7684826529fbdd82ae84de01c45f3b309f1cf41e096dab651ca261
4
- data.tar.gz: a345c80a98c564cf7de1cb62768214f8273abf033b1c24ebb8575bb69fe00026
3
+ metadata.gz: 1ca8294136b5eab8a344a40bf1d5ebb6d8ebf08f2794a1c6e1f582f593432049
4
+ data.tar.gz: c36d4c09a163ade8bfd860519a50519a28ae3f93121a14c482bf1bceb6239cde
5
5
  SHA512:
6
- metadata.gz: db09a0dc972eb58b3c90412be2a7503015ca3b32152ea2f4be89065833ed94d674fb4bce97a19a5ba8721b59be14b8c8beb00849db865d6b9ee9cfe3ea0afffc
7
- data.tar.gz: b97c8c53963c3a9460a38c06bde171fd5f9494c7bdbc8b575ded5b17a854b0038ac421da45628f42865d9cecef7824485adc80e92af9281541bac6dc96466102
6
+ metadata.gz: 1a085655336dcd4d06db5b51269f6b72d8d7174767f0b2990796d29cd410fb90adc884b86df3cf97087e75b35d387f655ed557628145c3af9badf17d3b29c50e
7
+ data.tar.gz: d8e0741301b1504f7714077ef435a987b51c520fcc3788f85acaf42ad3785fcec7bf435e897666a5f9a16050d2f503cfab5daa68525a1fc86a18b5219018a6ed
data/README.md CHANGED
@@ -5,31 +5,82 @@
5
5
  [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE.txt)
6
6
  [![DOI](https://zenodo.org/badge/281373245.svg)](https://zenodo.org/badge/latestdoi/281373245)
7
7
 
8
+
9
+ <img src="https://user-images.githubusercontent.com/5798442/182540876-c3ca2906-7d05-4c93-9107-ce4135ae9765.png" align="right">
10
+
8
11
  ## Installation
9
12
 
10
- Requirement: [IGV (Integrative Genomics Viewer)](http://software.broadinstitute.org/software/igv/) and [Ruby](https://github.com/ruby/ruby).
13
+ Requirement :
14
+
15
+ * [Ruby](https://github.com/ruby/ruby)
16
+ * [IGV (Integrative Genomics Viewer)](http://software.broadinstitute.org/software/igv/)
17
+ * [Enable IGV to listen on the port](https://software.broadinstitute.org/software/igv/Preferences#Advanced)
18
+ * View > Preference > Advanced > Enable port ☑
11
19
 
12
20
  ```ruby
13
21
  gem install ruby-igv
14
22
  ```
15
23
 
24
+ ## Quickstart
25
+
26
+ ```ruby
27
+ require 'igv'
28
+
29
+ igv = IGV.start # This launch IGV
30
+ igv.genome 'hg19'
31
+ igv.load 'http://hgdownload.cse.ucsc.edu/goldenPath/hg19/encodeDCC/' \
32
+ 'wgEncodeUwRepliSeq/wgEncodeUwRepliSeqK562G1AlnRep1.bam'
33
+ igv.go 'chr18:78,016,233-78,016,640'
34
+ igv.snapshot 'region.png'
35
+ igv.exit
36
+ ```
37
+
16
38
  ## Usage
17
39
 
18
- [Enable IGV to listen on the port](https://software.broadinstitute.org/software/igv/Preferences#Advanced): Preference > Advanced > Enable port
40
+ ### docs
41
+
42
+ See [the list of Batch commands](https://github.com/igvteam/igv/wiki/Batch-commands).
19
43
 
20
44
  ```ruby
21
- igv = IGV.new
22
- igv.genome 'hg19'
23
- igv.load 'http://hgdownload.cse.ucsc.edu/goldenPath/hg19/encodeDCC/wgEncodeUwRepliSeq/wgEncodeUwRepliSeqK562G1AlnRep1.bam'
24
- igv.go 'chr18:78,016,233-78,016,640'
25
- igv.save '/tmp/r/region.svg'
26
- igv.save '/tmp/r/region.png'
27
- igv.snapshot_dir = '/tmp/r2/'
28
- igv.save 'region.jpg' # save to /tmp/r2/region.png
29
- igv.send 'echo' # whatever you want
45
+ igv.commands # Show the IGV command reference in your browser
30
46
  ```
31
47
 
32
- See [the list of Batch commands](https://github.com/igvteam/igv/wiki/Batch-commands).
48
+ [docs](https://rubydoc.info/gems/ruby-igv)
49
+
50
+ ### send
51
+
52
+ Not all commands are implemented in Ruby. Commands that are not implemented can be sent using the send method.
53
+
54
+ ```ruby
55
+ igv.send("maxPanelHeight", 10)
56
+ ```
57
+
58
+
59
+ ### Launch IGV
60
+
61
+ Launch IGV from Ruby scripot.
62
+
63
+ ```ruby
64
+ igv = IGV.start # launch IGV app using spawn
65
+ ```
66
+
67
+ ### Open socket connection to IGV
68
+
69
+ ```ruby
70
+ igv = IGV.new # create an IGV object. Then you will type `igv.connect`
71
+ igv = IGV.open # create an IGV object and connect it to an already activated IGV.
72
+ ```
73
+
74
+ ### Close IGV
75
+
76
+ The behavior of the following methods is different.
77
+
78
+ ```ruby
79
+ igv.close # close the socket connection
80
+ igv.exit # send exit command to IGV
81
+ igv.quit # alias method to exit
82
+ igv.kill # kill group pid created with IGV.start
83
+ ```
33
84
 
34
85
  ## Contributing
35
86
 
data/lib/igv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class IGV
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.7'
5
5
  end
data/lib/igv.rb CHANGED
@@ -9,103 +9,292 @@ require 'fileutils'
9
9
  class IGV
10
10
  class Error < StandardError; end
11
11
 
12
- attr_reader :host, :port, :snapshot_dir, :history
12
+ attr_reader :host, :port, :history
13
13
 
14
14
  def initialize(host: '127.0.0.1', port: 60_151, snapshot_dir: Dir.pwd)
15
15
  @host = host
16
16
  @port = port
17
+ @snapshot_dir = File.expand_path(snapshot_dir)
17
18
  @history = []
18
- connect
19
- set_snapshot_dir(snapshot_dir)
20
19
  end
21
20
 
22
- # def self.start
23
- # end
21
+ def self.open(host: '127.0.0.1', port: 60_151, snapshot_dir: Dir.pwd)
22
+ igv = new(host: host, port: port, snapshot_dir: snapshot_dir)
23
+ igv.connect
24
+ return igv unless block_given?
24
25
 
25
- def connect
26
+ begin
27
+ yield igv
28
+ ensure
29
+ @socket&.close
30
+ end
31
+ igv
32
+ end
33
+
34
+ # Launch IGV from ruby script
35
+
36
+ def self.start(port: 60_151, command: 'igv')
37
+ r, w = IO.pipe
38
+ pid_igv = spawn(command, '-p', port.to_s, pgroup: true, out: w, err: w)
39
+ pgid_igv = Process.getpgid(pid_igv)
40
+ Process.detach(pid_igv)
41
+ puts "\e[33m"
42
+ while (line = r.gets.chomp("\n"))
43
+ puts line
44
+ break if line.include? "Listening on port #{port}"
45
+ end
46
+ puts "\e[0m"
47
+ igv = open(port: port)
48
+ igv.instance_variable_set(:@pgid_igv, pgid_igv)
49
+ igv
50
+ end
51
+
52
+ # Kill IGV process by process group id
53
+
54
+ def kill
55
+ if instance_variable_defined?(:@pgid_igv)
56
+ warn \
57
+ 'This method kills the process with the group ID specified at startup. ' \
58
+ 'Please use exit or quit if possible.'
59
+ else
60
+ warn \
61
+ 'The kill method terminates only IGV commands invoked by the start method.' \
62
+ 'Otherwise, use exit or quit.'
63
+ return
64
+ end
65
+ pgid = @pgid_igv
66
+ Process.kill(:TERM, -pgid)
67
+ close
68
+ end
69
+
70
+ # Connect to IGV server
71
+
72
+ def connect(host2 = @host, port2 = @port, connect_timeout: nil)
73
+ @socket&.close
74
+ @socket = Socket.tcp(host2, port2, connect_timeout: connect_timeout)
75
+ end
76
+
77
+ # Close the socket.
78
+ # This method dose not exit IGV.
79
+
80
+ def close
26
81
  @socket&.close
27
- @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
28
- addr = Socket.sockaddr_in(port, host)
29
- @socket.connect(addr)
30
82
  end
31
83
 
32
- def go(position)
33
- send "goto #{position}"
84
+ def closed?
85
+ return true if @socket.nil?
86
+
87
+ @socket.closed?
88
+ end
89
+
90
+ # Send batch commands to IGV.
91
+ # @param [String] cmds
92
+
93
+ def send(*cmds)
94
+ cmd = \
95
+ cmds
96
+ .compact
97
+ .map do |cmd|
98
+ case cmd
99
+ when String, Symbol, Numeric then cmd.to_s
100
+ when ->(c) { c.respond_to?(:to_str) } then cmd.to_str
101
+ else raise ArgumentError, "#{cmd.inspect} is not a string"
102
+ end.strip.encode(Encoding::UTF_8)
103
+ end
104
+ .join(' ')
105
+ @history << cmd
106
+ @socket.puts(cmd)
107
+ @socket.gets&.chomp("\n")
108
+ end
109
+
110
+ # Show IGV batch commands in the browser.
111
+ # https://github.com/igvteam/igv/wiki/Batch-commands
112
+
113
+ def commands
114
+ require 'launchy'
115
+ Launchy.open('https://github.com/igvteam/igv/wiki/Batch-commands')
116
+ end
117
+
118
+ # Writes the value of "param" back to the response
119
+ #
120
+ # @param param [String] The parameter to echo.
121
+ # @return [String] The value of "param". If param is not specified, "echo".
122
+
123
+ def echo(param = nil)
124
+ send :echo, param
34
125
  end
35
- alias goto go
126
+
127
+ # Selects a genome by id, or loads a genome (or indexed fasta) from the supplied path.
128
+ #
129
+ # @param name_or_path [String] The genome to load
36
130
 
37
131
  def genome(name_or_path)
38
132
  path = File.expand_path(name_or_path)
39
133
  if File.exist?(path)
40
- send "genome #{path}"
134
+ send :genome, path
41
135
  else
42
- send "genome #{name_or_path}"
136
+ send :genome, name_or_path
43
137
  end
44
138
  end
45
139
 
46
- def load(path_or_url)
47
- if URI.parse(path_or_url).scheme
48
- send "load #{path_or_url}"
49
- else
50
- send "load #{File.expand_path(path_or_url)}"
51
- end
140
+ # Loads a data or session file by specifying a full path to a local file or a URL.
141
+ #
142
+ # @param path_or_url [String] The path to a local file or a URL
143
+ # @param index [String] The index of the file
144
+
145
+ def load(path_or_url, index: nil)
146
+ path_or_url = if URI.parse(path_or_url).scheme
147
+ path_or_url
148
+ else
149
+ File.expand_path(path_or_url)
150
+ end
151
+ index = "index=#{index}" if index
152
+ send :load, path_or_url, index
153
+ end
154
+
155
+ # Go to the specified location
156
+ #
157
+ # @param location [String] The location to go to.
158
+
159
+ def goto(position)
160
+ send :goto, position
52
161
  end
162
+ alias go goto
53
163
 
54
- def region(contig, start, end_)
55
- send ['region', contig, start, end_].join(' ')
164
+ # Defines a region of interest bounded by the two loci
165
+ #
166
+ # @param chr [String] The chromosome of the region
167
+ # @param start [Integer] The start position of the region
168
+ # @param end_ [Integer] The end position of the region
169
+
170
+ def region(chr, start, end_)
171
+ send :region, chr, start, end_
56
172
  end
57
173
 
58
174
  def sort(option = 'base')
59
- unless %w[base position strand quality sample readGroup].include? option
60
- raise 'options is one of: base, position, strand, quality, sample, and readGroup.'
61
- end
175
+ vop = %w[base position strand quality sample readGroup]
176
+ raise "options is one of: #{vop.join(', ')}" unless vop.include? option
177
+
178
+ send :sort, option
179
+ end
62
180
 
63
- send "sort #{option}"
181
+ # Expands the given track.
182
+ #
183
+ # @param track [String] The track to expand.
184
+ # If not specified, expands all tracks.
185
+
186
+ def expand(track = nil)
187
+ send :expand, track
188
+ end
189
+
190
+ # Collapses a given track.
191
+ #
192
+ # @param track [String] The track to collapse.
193
+ # If not specified, collapses all tracks.
194
+
195
+ def collapse(track = nil)
196
+ send :collapse, track
64
197
  end
65
198
 
66
- def expand(track = '')
67
- send "expand #{track}"
199
+ # Squish a given track.
200
+ #
201
+ # @param track [String] The track to squish.
202
+ # If not specified, squishes all tracks.
203
+
204
+ def squish(track = nil)
205
+ send :squish, track
68
206
  end
69
207
 
70
- def collapse(track = '')
71
- send "collapse #{track}"
208
+ # Set the display mode for an alignment track to "View as pairs".
209
+ #
210
+ # @param track [String] The track to set.
211
+ # If not specified, sets all tracks.
212
+
213
+ def viewaspairs(track = nil)
214
+ send :viewaspairs, track
72
215
  end
73
216
 
74
217
  def clear
75
- send 'clear'
218
+ send :clear
76
219
  end
77
220
 
221
+ # Exit (close) the IGV application.
222
+
78
223
  def exit
79
- send 'exit'
224
+ send :exit
225
+ @socket.close
80
226
  end
81
227
  alias quit exit
82
228
 
83
- def send(cmd)
84
- @history << cmd
85
- @socket.puts(cmd.encode(Encoding::UTF_8))
86
- @socket.gets&.chomp("\n")
87
- end
229
+ # Sets the directory in which to write images.
230
+ # Retruns the current snapshot directory if no argument is given.
231
+ #
232
+ # @param path [String] The path to the directory.
233
+
234
+ def snapshot_dir(dir_path = nil)
235
+ return @snapshot_dir if dir_path.nil?
236
+
237
+ dir_path = File.expand_path(dir_path)
238
+ return if dir_path == @snapshot_dir
88
239
 
89
- def snapshot_dir=(snapshot_dir)
90
- snapshot_dir = File.expand_path(snapshot_dir)
91
- return if snapshot_dir == @snaphot_dir
240
+ r = snapshot_dir_internal(dir_path)
241
+ @snapshot_dir = dir_path
242
+ r
243
+ end
92
244
 
93
- FileUtils.mkdir_p(snapshot_dir)
94
- send "snapshotDirectory #{snapshot_dir}"
95
- @snapshot_dir = snapshot_dir
245
+ private def snapshot_dir_internal(dir_path)
246
+ dir_path = File.expand_path(dir_path)
247
+ FileUtils.mkdir_p(dir_path)
248
+ send :snapshotDirectory, dir_path
96
249
  end
97
- alias set_snapshot_dir snapshot_dir=
98
250
 
99
- def save(file_path = nil)
100
- if file_path
101
- # igv assumes the path is just a single filename, but
102
- # we can set the snapshot dir. then just use the filename.
103
- dir_path = File.dirname(file_path)
104
- set_snapshot_dir(File.expand_path(dir_path)) if dir_path != '.'
105
- send "snapshot #{File.basename(file_path)}"
251
+ # Saves a snapshot of the IGV window to an image file.
252
+ # If filename is omitted, writes a PNG file with a filename generated based on the locus.
253
+ # If filename is specified, the filename extension determines the image file format,
254
+ # which must be either .png or .svg.
255
+ # @note In Ruby-IGV, it is possible to pass absolute or relative paths as well as file names;
256
+ # the Snapshot directory is set to Dir.pwd by default.
257
+ #
258
+ # @param file_path [String] The path to the image file.
259
+
260
+ def snapshot(file_path = nil)
261
+ return send(:snapshot) if file_path.nil?
262
+
263
+ dir_path = File.dirname(file_path)
264
+ filename = File.basename(file_path)
265
+ if dir_path != @snapshot_dir
266
+ snapshot_dir_internal(dir_path)
267
+ r = send :snapshot, filename
268
+ snapshot_dir_internal(@snapshot_dir)
269
+ r
106
270
  else
107
- send 'snapshot'
271
+ send :snapshot, filename
108
272
  end
109
273
  end
110
- alias snapshot save
274
+
275
+ # Temporarily set the preference named key to the specified value.
276
+ #
277
+ # @param key [String] The preference name
278
+ # @param value [String] The preference value
279
+
280
+ def preferences(key, value)
281
+ send :preferences, key, value
282
+ end
283
+
284
+ # Show "preference.tab" in your browser.
285
+
286
+ def show_preferences_table
287
+ require 'launchy'
288
+ Launchy.open('https://raw.githubusercontent.com/igvteam/igv/master/src/main/resources/org/broad/igv/prefs/preferences.tab')
289
+ end
290
+
291
+ # Save the current session.
292
+ # It is recommended that a full path be used for filename. IGV release 2.11.1
293
+ #
294
+ # @param filename [String] The path to the session file
295
+
296
+ def save_session(file_path)
297
+ file_path = File.expand_path(file_path)
298
+ send :saveSession, file_path
299
+ end
111
300
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-igv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - kojix2
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-02 00:00:00.000000000 Z
11
+ date: 2022-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: launchy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement