ruby-igv 0.0.5 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -13
  3. data/lib/igv/version.rb +1 -1
  4. data/lib/igv.rb +305 -53
  5. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a162dd0aa5d26fd45b3da87eb9c8e4faec6bc47f6922dc850dc5f5f556405c26
4
- data.tar.gz: 6ceb747c0f8b5b697f561c2836c8e021d29fd2bcc0cc5bc91cc7916bc24b0c61
3
+ metadata.gz: ed0319fd387ae14300d7f596b0a0e2a6aa6cd1dab63a67921070e66446b532db
4
+ data.tar.gz: 7d3e5587f800b76b5f96c9fccb44c210e8b36ee6fb598a368b686bff6c1745e7
5
5
  SHA512:
6
- metadata.gz: 0e05cd137522d371629790616283c5f2c31f96f9882979d64f46ab83530ccf14569bc039213ddda103653ad009d9818e50f1e95e5f3738cb985903da6d8c6ed2
7
- data.tar.gz: 361d5f9e770f4f78304e4b99672e6e5e2c116bd8994b5d57076b808b40b27ae5fa493c74842923d4165adef2b79827ae85e5d711d9ce279f001afddb0676df95
6
+ metadata.gz: e85423c90acd6a974e5b29a1890a1177823785043b6bae0736d6f234c9bcdb6b57f2dc22858ccfdc53af270e367b9576f309f6235d6754bce2d71b6ed7fd90dc
7
+ data.tar.gz: 445d5ce3a05e5d876ac4813cce4fdaad9bee4fe568a706838a97f8958cb247429bad6cf996072e68b246fa5a345e1827b45bf9210b8f12890550d8bd6b00b6d9
data/README.md CHANGED
@@ -1,32 +1,92 @@
1
- # Ruby-IGV
1
+ # ruby-igv
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/ruby-igv.svg)](https://badge.fury.io/rb/ruby-igv)
4
4
  [![Docs Latest](https://img.shields.io/badge/docs-latest-blue.svg)](https://rubydoc.info/gems/ruby-igv)
5
5
  [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE.txt)
6
+ [![DOI](https://zenodo.org/badge/281373245.svg)](https://zenodo.org/badge/latestdoi/281373245)
6
7
 
7
- Controlling [Integrative Genomics Viewer (IGV)](http://software.broadinstitute.org/software/igv/) with the [Ruby](https://github.com/ruby/ruby) language.
8
+ Ruby-IGV is a simple tool for controlling the Integrated Genomics Viewer (IGV) from the Ruby language. It provides an automated way to load files, specify genome locations, and take and save screenshots using IGV.
9
+
10
+ <img src="https://user-images.githubusercontent.com/5798442/182540876-c3ca2906-7d05-4c93-9107-ce4135ae9765.png" align="right">
8
11
 
9
12
  ## Installation
10
13
 
14
+ Requirement :
15
+
16
+ * [Ruby](https://github.com/ruby/ruby)
17
+ * [IGV (Integrative Genomics Viewer)](http://software.broadinstitute.org/software/igv/)
18
+ * [Enable IGV to listen on the port](https://software.broadinstitute.org/software/igv/Preferences#Advanced)
19
+ * View > Preference > Advanced > Enable port ☑
20
+
11
21
  ```ruby
12
22
  gem install ruby-igv
13
23
  ```
14
24
 
25
+ ## Quickstart
26
+
27
+ <img src="https://user-images.githubusercontent.com/5798442/182623864-a9fa59aa-abb9-4cb1-8311-2b3479b7414e.png" width="300" align="left">
28
+
29
+ ```ruby
30
+ require 'igv'
31
+
32
+ igv = IGV.start # This launch IGV
33
+
34
+ igv.set :SleepInterval, 200 # give a time interval
35
+ igv.genome 'hg19'
36
+ igv.load 'http://hgdownload.cse.ucsc.edu/goldenPath/' \
37
+ 'hg19/encodeDCC/wgEncodeUwRepliSeq/' \
38
+ 'wgEncodeUwRepliSeqK562G1AlnRep1.bam'
39
+ igv.go 'chr18:78016233-78016640'
40
+ igv.snapshot 'region.png'
41
+ ```
42
+
15
43
  ## Usage
16
44
 
45
+ ### commands
46
+
47
+ The commonly used commands in IGV are summarized in the official [list of Batch commands](https://github.com/igvteam/igv/wiki/Batch-commands). (but even this does not seem to be all of them). You can also call the `commands` method from Ruby to open a browser and view the list.
48
+
17
49
  ```ruby
18
- igv = IGV.new
19
- igv.genome 'hg19'
20
- igv.load 'http://hgdownload.cse.ucsc.edu/goldenPath/hg19/encodeDCC/wgEncodeUwRepliSeq/wgEncodeUwRepliSeqK562G1AlnRep1.bam'
21
- igv.go 'chr18:78,016,233-78,016,640'
22
- igv.save '/tmp/r/region.svg'
23
- igv.save '/tmp/r/region.png'
24
- igv.snapshot_dir = '/tmp/r2/'
25
- igv.save 'region.jpg' # save to /tmp/r2/region.png
26
- igv.send 'echo' # whatever you want
50
+ igv.commands # Show the IGV command reference in your browser
27
51
  ```
28
52
 
29
- * [Controlling IGV through a Port](https://software.broadinstitute.org/software/igv/PortCommands)
53
+ ### docs
54
+
55
+ See [docs](https://rubydoc.info/gems/ruby-igv/IGV). Not all commands are implemented in Ruby.
56
+
57
+ ### send
58
+
59
+ Commands that are not implemented can be sent using the send method.
60
+
61
+ ```ruby
62
+ igv.send("maxPanelHeight", 10)
63
+ ```
64
+
65
+ ### Launch IGV
66
+
67
+ Launch IGV from Ruby script.
68
+
69
+ ```ruby
70
+ igv = IGV.start # launch IGV app using spawn
71
+ ```
72
+
73
+ ### Open socket connection to IGV
74
+
75
+ ```ruby
76
+ igv = IGV.new # create an IGV object. Then you will type `igv.connect`
77
+ igv = IGV.open # create an IGV object and connect it to an already activated IGV.
78
+ ```
79
+
80
+ ### Close IGV
81
+
82
+ The behavior of the following methods is different.
83
+
84
+ ```ruby
85
+ igv.close # close the socket connection
86
+ igv.exit # send exit command to IGV then close the socket connection
87
+ igv.quit # alias method to exit
88
+ igv.kill # kill group pid created with IGV.start
89
+ ```
30
90
 
31
91
  ## Contributing
32
92
 
@@ -35,7 +95,13 @@ igv.send 'echo' # whatever you want
35
95
  * Write, clarify, or fix documentation
36
96
  * Suggest or add new features
37
97
 
38
- # Acknowledgement
98
+ ```
99
+ Do you need commit rights to my repository?
100
+ Do you want to get admin rights and take over the project?
101
+ If so, please feel free to contact me @kojix2.
102
+ ```
103
+
104
+ ## Acknowledgement
39
105
  This gem is strongly inspired by a Python script developed by Brent Pedersen.
40
106
  * [brentp/bio-playground/igv](https://github.com/brentp/bio-playground).
41
107
 
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.5'
4
+ VERSION = '0.0.8'
5
5
  end
data/lib/igv.rb CHANGED
@@ -9,103 +9,355 @@ 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
+
14
+ # Create IGV client object
15
+ #
16
+ # @param host [String] hostname or IP address of IGV server
17
+ # @param port [Integer] port number of IGV server
18
+ # @param path [String] directory path to save snapshots
19
+ # @return [IGV] IGV client object
13
20
 
14
21
  def initialize(host: '127.0.0.1', port: 60_151, snapshot_dir: Dir.pwd)
22
+ raise ArgumentError, 'new dose not take a block' if block_given?
23
+
15
24
  @host = host
16
25
  @port = port
26
+ @snapshot_dir = File.expand_path(snapshot_dir)
17
27
  @history = []
18
- connect
19
- set_snapshot_dir(snapshot_dir)
20
28
  end
21
29
 
22
- # def self.start
23
- # end
30
+ # Create IGV object and connect to IGV server
31
+ # This method accepts a block.
32
+ #
33
+ # @param host [String] hostname or IP address of IGV server
34
+ # @param port [Integer] port number of IGV server
35
+ # @param path [String] directory path to save snapshots
36
+ # @return [IGV] IGV client object
37
+ # @yield [IGV] IGV client object
38
+
39
+ def self.open(host: '127.0.0.1', port: 60_151, snapshot_dir: Dir.pwd)
40
+ igv = new(host: host, port: port, snapshot_dir: snapshot_dir)
41
+ igv.connect
42
+ return igv unless block_given?
43
+
44
+ begin
45
+ yield igv
46
+ ensure
47
+ @socket&.close
48
+ end
49
+ igv
50
+ end
51
+
52
+ def self.port_open?(port)
53
+ !system("lsof -i:#{port}", out: '/dev/null')
54
+ end
55
+ private_class_method :port_open?
56
+
57
+ # Launch IGV from ruby script
58
+ #
59
+ # @param port [Integer] port number
60
+ # @param command [String] command to launch IGV
61
+ # @param snapshot_dir [String] directory path to save snapshots
62
+ # @return [IGV] IGV client object
63
+
64
+ def self.start(port: 60_151, command: 'igv', snapshot_dir: Dir.pwd)
65
+ case port_open?(port)
66
+ when nil then warn "Cannot tell if port #{port} is open"
67
+ when false then raise("Port #{port} is already in use")
68
+ when true then warn "Port #{port} is available"
69
+ end
70
+ r, w = IO.pipe
71
+ pid_igv = spawn(command, '-p', port.to_s, pgroup: true, out: w, err: w)
72
+ pgid_igv = Process.getpgid(pid_igv)
73
+ Process.detach(pid_igv)
74
+ puts "\e[33m"
75
+ while (line = r.gets.chomp("\n"))
76
+ puts line
77
+ break if line.include? "Listening on port #{port}"
78
+ end
79
+ puts "\e[0m"
80
+ igv = open(port: port, snapshot_dir: snapshot_dir)
81
+ igv.instance_variable_set(:@pgid_igv, pgid_igv)
82
+ igv
83
+ end
84
+
85
+ # Kill IGV process by process group id
86
+
87
+ def kill
88
+ if instance_variable_defined?(:@pgid_igv)
89
+ warn \
90
+ 'This method kills the process with the group ID specified at startup. ' \
91
+ 'Please use exit or quit if possible.'
92
+ else
93
+ warn \
94
+ 'The kill method terminates only IGV commands invoked by the start method.' \
95
+ 'Otherwise, use exit or quit.'
96
+ return
97
+ end
98
+ pgid = @pgid_igv
99
+ Process.kill(:TERM, -pgid)
100
+ close
101
+ end
102
+
103
+ # Connect to IGV server
24
104
 
25
- def connect
105
+ def connect(host2 = @host, port2 = @port, connect_timeout: nil)
26
106
  @socket&.close
27
- @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
28
- addr = Socket.sockaddr_in(port, host)
29
- @socket.connect(addr)
107
+ @socket = Socket.tcp(host2, port2, connect_timeout: connect_timeout)
108
+ end
109
+
110
+ # Close the socket.
111
+ # This method dose not exit IGV.
112
+
113
+ def close
114
+ @socket&.close
115
+ end
116
+
117
+ def closed?
118
+ return true if @socket.nil?
119
+
120
+ @socket.closed?
121
+ end
122
+
123
+ # Send batch commands to IGV.
124
+ #
125
+ # @param *cmds [String, Symbol, Numeric] batch commands
126
+ # @return [String] response from IGV
127
+
128
+ def send(*cmds)
129
+ cmd = \
130
+ cmds
131
+ .compact
132
+ .map do |cmd|
133
+ case cmd
134
+ when String, Symbol, Numeric then cmd.to_s
135
+ when ->(c) { c.respond_to?(:to_str) } then cmd.to_str
136
+ else raise ArgumentError, "#{cmd.inspect} is not a string"
137
+ end.strip.encode(Encoding::UTF_8)
138
+ end
139
+ .join(' ')
140
+ @history << cmd
141
+ @socket.puts(cmd)
142
+ @socket.gets&.chomp("\n")
143
+ end
144
+
145
+ # Syntactic sugar for IGV commands that begin with set.
146
+ #
147
+ # @example
148
+ # igv.set :SleepInterval, 100
149
+ # igv.send "setSleepInterval", 100 # same as above
150
+ # @param cmd [String, Symbol] batch commands
151
+ # @param *params [String, Symbol, Numeric] batch commands
152
+
153
+ def set(cmd, *params)
154
+ cmd = "set#{cmd}"
155
+ send(cmd, *params)
156
+ end
157
+
158
+ # Show IGV batch commands in the browser.
159
+ # https://github.com/igvteam/igv/wiki/Batch-commands
160
+
161
+ def commands
162
+ require 'launchy'
163
+ Launchy.open('https://github.com/igvteam/igv/wiki/Batch-commands')
30
164
  end
31
165
 
32
- def go(position)
33
- send "goto #{position}"
166
+ # Writes the value of "param" back to the response
167
+ # @note IGV Batch command
168
+ #
169
+ # @param param [String] The parameter to echo.
170
+ # @return [String] The value of "param". If param is not specified, "echo".
171
+
172
+ def echo(param = nil)
173
+ send :echo, param
34
174
  end
35
- alias goto go
175
+
176
+ # Selects a genome by id, or loads a genome (or indexed fasta) from the supplied path.
177
+ # @note IGV Batch command
178
+ #
179
+ # @param name_or_path [String] The genome to load
36
180
 
37
181
  def genome(name_or_path)
38
182
  path = File.expand_path(name_or_path)
39
183
  if File.exist?(path)
40
- send "genome #{path}"
184
+ send :genome, path
41
185
  else
42
- send "genome #{name_or_path}"
186
+ send :genome, name_or_path
43
187
  end
44
188
  end
45
189
 
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
190
+ # Loads a data or session file by specifying a full path to a local file or a URL.
191
+ # @note IGV Batch command
192
+ #
193
+ # @param path_or_url [String] The path to a local file or a URL
194
+ # @param index [String] The index of the file
195
+
196
+ def load(path_or_url, index: nil)
197
+ path_or_url = if URI.parse(path_or_url).scheme
198
+ path_or_url
199
+ else
200
+ File.expand_path(path_or_url)
201
+ end
202
+ index = "index=#{index}" if index
203
+ send :load, path_or_url, index
52
204
  end
53
205
 
54
- def region(contig, start, end_)
55
- send ['region', contig, start, end_].join(' ')
206
+ # Go to the specified location
207
+ # @note IGV Batch command
208
+ #
209
+ # @param location [String] The location to go to.
210
+
211
+ def goto(position)
212
+ send :goto, position
213
+ end
214
+ alias go goto
215
+
216
+ # Defines a region of interest bounded by the two loci
217
+ # @note IGV Batch command
218
+ #
219
+ # @param chr [String] The chromosome of the region
220
+ # @param start [Integer] The start position of the region
221
+ # @param end_ [Integer] The end position of the region
222
+
223
+ def region(chr, start, end_)
224
+ send :region, chr, start, end_
56
225
  end
57
226
 
58
227
  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
228
+ vop = %w[base position strand quality sample readGroup]
229
+ raise "options is one of: #{vop.join(', ')}" unless vop.include? option
62
230
 
63
- send "sort #{option}"
231
+ send :sort, option
64
232
  end
65
233
 
66
- def expand(_track = '')
67
- send "expand #{track}"
234
+ # Expands the given track.
235
+ # @note IGV Batch command
236
+ #
237
+ # @param track [String] The track to expand.
238
+ # If not specified, expands all tracks.
239
+
240
+ def expand(track = nil)
241
+ send :expand, track
68
242
  end
69
243
 
70
- def collapse(_track = '')
71
- send "collapse #{track}"
244
+ # Collapses a given track.
245
+ # @note IGV Batch command
246
+ #
247
+ # @param track [String] The track to collapse.
248
+ # If not specified, collapses all tracks.
249
+
250
+ def collapse(track = nil)
251
+ send :collapse, track
252
+ end
253
+
254
+ # Squish a given track.
255
+ # @note IGV Batch command
256
+ #
257
+ # @param track [String] The track to squish.
258
+ # If not specified, squishes all tracks.
259
+
260
+ def squish(track = nil)
261
+ send :squish, track
72
262
  end
73
263
 
264
+ # Set the display mode for an alignment track to "View as pairs".
265
+ #
266
+ # @param track [String] The track to set.
267
+ # If not specified, sets all tracks.
268
+
269
+ def viewaspairs(track = nil)
270
+ send :viewaspairs, track
271
+ end
272
+
273
+ # @note IGV Batch command
274
+
74
275
  def clear
75
- send 'clear'
276
+ send :clear
76
277
  end
77
278
 
279
+ # Exit (close) the IGV application and close the socket.
280
+ # @note IGV Batch command (modifyed)
281
+
78
282
  def exit
79
- send 'exit'
283
+ send :exit
284
+ @socket.close
80
285
  end
81
286
  alias quit exit
82
287
 
83
- def send(cmd)
84
- @history << cmd
85
- @socket.puts(cmd.encode(Encoding::UTF_8))
86
- @socket.gets&.chomp("\n")
87
- end
288
+ # Sets the directory in which to write images.
289
+ # Retruns the current snapshot directory if no argument is given.
290
+ # @note IGV Batch command (modified)
291
+ #
292
+ # @param path [String] The path to the directory.
293
+
294
+ def snapshot_dir(dir_path = nil)
295
+ return @snapshot_dir if dir_path.nil?
88
296
 
89
- def snapshot_dir=(snapshot_dir)
90
- snapshot_dir = File.expand_path(snapshot_dir)
91
- return if snapshot_dir == @snaphot_dir
297
+ dir_path = File.expand_path(dir_path)
298
+ return if dir_path == @snapshot_dir
92
299
 
93
- FileUtils.mkdir_p(snapshot_dir)
94
- send "snapshotDirectory #{snapshot_dir}"
95
- @snapshot_dir = snapshot_dir
300
+ r = snapshot_dir_internal(dir_path)
301
+ @snapshot_dir = dir_path
302
+ r
96
303
  end
97
- alias set_snapshot_dir snapshot_dir=
98
304
 
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)}"
305
+ private def snapshot_dir_internal(dir_path)
306
+ dir_path = File.expand_path(dir_path)
307
+ FileUtils.mkdir_p(dir_path)
308
+ send :snapshotDirectory, dir_path
309
+ end
310
+
311
+ # Saves a snapshot of the IGV window to an image file.
312
+ # If filename is omitted, writes a PNG file with a filename generated based on the locus.
313
+ # If filename is specified, the filename extension determines the image file format,
314
+ # which must be either .png or .svg.
315
+ # @note IGV Batch command (modified)
316
+ # @note In Ruby-IGV, it is possible to pass absolute or relative paths as well as file names;
317
+ # the Snapshot directory is set to Dir.pwd by default.
318
+ #
319
+ # @param file_path [String] The path to the image file.
320
+
321
+ def snapshot(file_path = nil)
322
+ return send(:snapshot) if file_path.nil?
323
+
324
+ dir_path = File.dirname(file_path)
325
+ filename = File.basename(file_path)
326
+ if dir_path != @snapshot_dir
327
+ snapshot_dir_internal(dir_path)
328
+ r = send :snapshot, filename
329
+ snapshot_dir_internal(@snapshot_dir)
330
+ r
106
331
  else
107
- send 'snapshot'
332
+ send :snapshot, filename
108
333
  end
109
334
  end
110
- alias snapshot save
335
+
336
+ # Temporarily set the preference named key to the specified value.
337
+ # @note IGV Batch command
338
+ #
339
+ # @param key [String] The preference name
340
+ # @param value [String] The preference value
341
+
342
+ def preferences(key, value)
343
+ send :preferences, key, value
344
+ end
345
+
346
+ # Show "preference.tab" in your browser.
347
+
348
+ def show_preferences_table
349
+ require 'launchy'
350
+ Launchy.open('https://raw.githubusercontent.com/igvteam/igv/master/src/main/resources/org/broad/igv/prefs/preferences.tab')
351
+ end
352
+
353
+ # Save the current session.
354
+ # It is recommended that a full path be used for filename. IGV release 2.11.1
355
+ # @note IGV Batch command
356
+ #
357
+ # @param filename [String] The path to the session file
358
+
359
+ def save_session(file_path)
360
+ file_path = File.expand_path(file_path)
361
+ send :saveSession, file_path
362
+ end
111
363
  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.5
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - kojix2
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-10 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
@@ -96,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
110
  - !ruby/object:Gem::Version
97
111
  version: '0'
98
112
  requirements: []
99
- rubygems_version: 3.1.4
113
+ rubygems_version: 3.3.7
100
114
  signing_key:
101
115
  specification_version: 4
102
116
  summary: Control IGV (Integrative Genomics Viewer) with Ruby.