@sjcrh/proteinpaint-rust 2.195.0 → 2.196.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.
package/src/readHDF5.rs DELETED
@@ -1,1188 +0,0 @@
1
- //------------------------------------------------------------------------------
2
- // readHDF5.rs - HDF5 Gene Expression Data Reader
3
- //------------------------------------------------------------------------------
4
- //
5
- // Extracts gene expression values from HDF5 files in dense or sparse formats.
6
- // Supports single genes with memory optimization and multiple genes with
7
- // parallel processing.
8
- //
9
- // Features:
10
- // - Auto format detection (dense/sparse)
11
- // - Optimized single and multi-gene queries
12
- // - Parallel processing for multiple genes
13
- // - JSON output with timing metrics
14
- //
15
- // Usage:
16
- // HDF5_DIR=/usr/local/Homebrew/Cellar/hdf5/1.14.3_1 &&
17
- // echo $json='{"gene":"TP53","hdf5_file":"matrix.h5"}' | target/release/readHDF5
18
- //------------------------------------------------------------------------------
19
- use hdf5::types::{FixedAscii, VarLenAscii};
20
- use hdf5::{File, Result};
21
- use ndarray::Dim;
22
- use ndarray::{s, Array1};
23
- use rayon::prelude::*;
24
- use serde_json::{json, Map, Value};
25
- use std::io;
26
- use std::sync::Arc;
27
- use std::time::Instant;
28
-
29
- /// Determines the format of an HDF5 gene expression file
30
- ///
31
- /// Examines the structure of an HDF5 file to detect its format:
32
- /// - "dense": Contains "counts", "gene_names", and "samples" datasets
33
- /// - "sparse": Contains "data" group and "sample_names" dataset
34
- /// - "unknown": Does not match either format
35
- ///
36
- /// # Arguments
37
- /// * `hdf5_filename` - Path to the HDF5 file to analyze
38
- ///
39
- /// # Returns
40
- /// The detected format as a static string: "dense", "sparse", or "unknown"
41
- fn detect_hdf5_format(hdf5_filename: &str) -> Result<&'static str> {
42
- let file = File::open(hdf5_filename)?;
43
-
44
- // Check for dense format (has counts, gene_names, and samples datasets)
45
- let has_counts = file.dataset("counts").is_ok();
46
- let has_gene_names = file.dataset("gene_names").is_ok();
47
- let has_samples = file.dataset("samples").is_ok();
48
-
49
- // Check for sparse matrix format (has data group and sample_names)
50
- let has_data_group = file.group("data").is_ok();
51
- let has_sample_names = file.dataset("sample_names").is_ok();
52
-
53
- if has_counts && has_gene_names && has_samples {
54
- // eprintln!("Dense format detected");
55
- Ok("dense")
56
- } else if has_data_group && has_sample_names {
57
- //eprintln!("Sparse format detected");
58
- Ok("sparse")
59
- } else {
60
- eprintln!("Unknown format detected");
61
- Ok("unknown")
62
- }
63
- }
64
-
65
- /// Unified function for querying gene expression data from an HDF5 file
66
- ///
67
- /// Automatically detects file format (dense or sparse) and routes to the appropriate handler.
68
- ///
69
- /// # Arguments
70
- /// * `hdf5_filename` - Path to the HDF5 file containing gene expression data
71
- /// * `gene_name` - Name of the gene whose expression values to extract
72
- ///
73
- /// # Returns
74
- /// Outputs gene expression data in JSON format to stdout
75
- fn query_gene(hdf5_filename: String, gene_name: String) -> Result<()> {
76
- // First, detect the file format
77
- let file_format = detect_hdf5_format(&hdf5_filename)?;
78
-
79
- // Query gene data based on format
80
- match file_format {
81
- "dense" => query_gene_dense(hdf5_filename, gene_name),
82
- "sparse" => query_gene_sparse(hdf5_filename, gene_name),
83
- _ => {
84
- println!(
85
- "{}",
86
- serde_json::json!({
87
- "status": "failure",
88
- "message": "Cannot query gene in unknown file format. Please use .h5 format in either sparse or dense format.",
89
- "file_path": hdf5_filename,
90
- "gene": gene_name,
91
- "format": "unknown"
92
- })
93
- );
94
- Ok(())
95
- }
96
- }
97
- }
98
-
99
- /// Reads expression data for a specific gene from a dense format HDF5 file
100
- ///
101
- /// Dense format contains "gene_ids", "samples", and "counts" datasets.
102
- ///
103
- /// # Arguments
104
- /// * `hdf5_filename` - Path to the HDF5 file
105
- /// * `gene_name` - Name of the gene to query
106
- ///
107
- /// # Returns
108
- /// Prints gene expression data in JSON format to stdout
109
- ///
110
- /// # Error Handling
111
- /// Handles file access issues, missing datasets, and gene not found scenarios
112
- fn query_gene_dense(hdf5_filename: String, gene_name: String) -> Result<()> {
113
- let file = match File::open(hdf5_filename) {
114
- Ok(f) => f,
115
- Err(err) => {
116
- println!(
117
- "{}",
118
- serde_json::json!({
119
- "status": "error",
120
- "message": format!("Failed to open HDF5 file: {}", err)
121
- })
122
- );
123
- return Ok(());
124
- }
125
- };
126
-
127
- let genes_dataset = match file.dataset("gene_ids") {
128
- Ok(ds) => ds,
129
- Err(err) => {
130
- println!(
131
- "{}",
132
- serde_json::json!({
133
- "status": "error",
134
- "message": format!("Failed to open gene_ids dataset {}", err)
135
- })
136
- );
137
- return Ok(());
138
- }
139
- };
140
-
141
- let genes_varlen = match genes_dataset.read_1d::<VarLenAscii>() {
142
- Ok(g) => g,
143
- Err(err) => {
144
- println!(
145
- "{}",
146
- serde_json::json!({
147
- "status": "error",
148
- "message": format!("Failed to read gene names as VarLenAscii: {}", err)
149
- })
150
- );
151
- return Ok(());
152
- }
153
- };
154
-
155
- // Convert to Vec<String> for easier handling
156
- let genes: Vec<String> = genes_varlen.iter().map(|g| g.to_string()).collect();
157
-
158
- let samples_dataset = match file.dataset("samples") {
159
- Ok(ds) => ds,
160
- Err(err) => {
161
- println!(
162
- "{}",
163
- serde_json::json!({
164
- "status": "error",
165
- "message": format!("Failed to open samples dataset{}", err)
166
- })
167
- );
168
- return Ok(());
169
- }
170
- };
171
-
172
- let samples_varlen = match samples_dataset.read_1d::<VarLenAscii>() {
173
- Ok(s) => s,
174
- Err(err) => {
175
- println!(
176
- "{}",
177
- serde_json::json!({
178
- "status": "error",
179
- "message": format!("Failed to read samples as VarLenAscii: {}", err)
180
- })
181
- );
182
- return Ok(());
183
- }
184
- };
185
-
186
- // Convert to Vec<String> for easier handling
187
- let samples: Vec<String> = samples_varlen.iter().map(|s| s.to_string()).collect();
188
-
189
- // Find the index of the requested gene
190
- let gene_index = match genes.iter().position(|x| *x == gene_name) {
191
- Some(index) => index,
192
- None => {
193
- println!(
194
- "{}",
195
- serde_json::json!({
196
- "status": "error",
197
- "message": format!("Gene '{}' not found in the dataset", gene_name)
198
- })
199
- );
200
- return Ok(());
201
- }
202
- };
203
-
204
- let counts_dataset = match file.dataset("counts") {
205
- Ok(ds) => ds,
206
- Err(err) => {
207
- println!(
208
- "{}",
209
- serde_json::json!({
210
- "status": "error",
211
- "message": format!("Failed to open counts dataset: {}", err)
212
- })
213
- );
214
- return Ok(());
215
- }
216
- };
217
-
218
- if gene_index >= counts_dataset.shape()[0] {
219
- println!(
220
- "{}",
221
- serde_json::json!({
222
- "status": "error",
223
- "message": "Gene index is out of bounds for the dataset"
224
- })
225
- );
226
- return Ok(());
227
- }
228
-
229
- let gene_expression: Array1<f64>;
230
-
231
- // Method 1: Try to read a 1D slice directly (for 2D datasets)
232
- match counts_dataset.read_slice_1d::<f64, _>(s![gene_index, ..]) {
233
- Ok(data) => {
234
- gene_expression = data;
235
- }
236
- Err(err1) => {
237
- // Method 2: Try a different approach
238
- let dataset_shape = counts_dataset.shape();
239
- if dataset_shape.len() != 2 {
240
- println!(
241
- "{}",
242
- serde_json::json!({
243
- "status": "error",
244
- "message": "Expected a 2D dataset for counts"
245
- })
246
- );
247
- return Ok(());
248
- }
249
-
250
- // Try reading the entire dataset and then extracting the row
251
- match counts_dataset.read::<f64, Dim<[usize; 2]>>() {
252
- Ok(all_data) => {
253
- // Extract just the row we need
254
- let row = all_data.slice(s![gene_index, ..]).to_owned();
255
- gene_expression = row;
256
-
257
- let mut output_string = String::from("{\"samples\":{");
258
-
259
- // Create direct key-value pairs where sample names are the keys
260
- for i in 0..gene_expression.len() {
261
- // Add each sample name as a key pointing directly to its expression value
262
- output_string += &format!("\"{}\":{}", samples[i].to_string(), gene_expression[i].to_string());
263
-
264
- // Add comma if not the last item
265
- if i < gene_expression.len() - 1 {
266
- output_string += ",";
267
- }
268
- }
269
-
270
- // Close the JSON object
271
- output_string += "}}";
272
- }
273
- Err(err2) => {
274
- println!(
275
- "{}",
276
- serde_json::json!({
277
- "status": "error",
278
- "message": format!("Failed to read expression values: {:?}, {:?}", err1, err2)
279
- })
280
- );
281
- return Ok(());
282
- }
283
- }
284
- }
285
- }
286
- let mut samples_map = Map::new();
287
- for (i, sample) in samples.iter().enumerate() {
288
- if i < gene_expression.len() {
289
- let value = if gene_expression[i].is_finite() {
290
- Value::from(gene_expression[i])
291
- } else {
292
- Value::Null
293
- };
294
-
295
- samples_map.insert(sample.replace("\\", ""), value);
296
- }
297
- }
298
-
299
- let output_json = json!({
300
- "gene": gene_name,
301
- "dataId": gene_name,
302
- "samples": samples_map
303
- });
304
-
305
- // Output the JSON directly
306
- println!("{}", output_json);
307
-
308
- Ok(())
309
- }
310
-
311
- /// Reads expression data for a specific gene from a sparse format HDF5 file
312
- ///
313
- /// Extracts expression values from sparse matrix HDF5 files using Compressed
314
- /// Sparse Column (CSC) structure.
315
- ///
316
- /// # Arguments
317
- /// * `hdf5_filename` - Path to the HDF5 file
318
- /// * `gene_name` - Name of the gene to query
319
- ///
320
- /// # Returns
321
- /// Prints gene expression data as JSON to stdout with "output_string:" prefix.
322
- /// Sample names are keys, expression values are values.
323
- ///
324
- /// The sparse format includes:
325
- /// - "data/dim" - Matrix dimensions
326
- /// - "gene_names" - Gene identifiers
327
- /// - "sample_names" - Sample identifiers
328
- /// - "data/p", "data/i", "data/x" - CSC matrix components
329
- fn query_gene_sparse(hdf5_filename: String, gene_name: String) -> Result<()> {
330
- let file = File::open(&hdf5_filename)?;
331
- let ds_dim = file.dataset("data/dim")?;
332
-
333
- // Check the data type and read the dataset accordingly
334
- let data_dim: Array1<_> = ds_dim.read::<usize, Dim<[usize; 1]>>()?;
335
- let num_samples = data_dim[0]; // Number of total columns in the dataset
336
- let num_genes = data_dim[1];
337
- println!("num_samples:{}", num_samples);
338
- println!("num_genes:{}", num_genes);
339
-
340
- let now_genes = Instant::now();
341
- let ds_genes = file.dataset("gene_names")?;
342
- let genes = ds_genes.read_1d::<FixedAscii<104>>()?;
343
- println!("Time for parsing genes:{:?}", now_genes.elapsed());
344
-
345
- let now_samples = Instant::now();
346
- let ds_samples = file.dataset("sample_names")?;
347
- let samples = ds_samples.read_1d::<FixedAscii<104>>()?;
348
- println!("Time for parsing samples:{:?}", now_samples.elapsed());
349
-
350
- let gene_index = match genes.iter().position(|&x| x == gene_name) {
351
- Some(index) => {
352
- println!(
353
- "The index of '{}' is {} in 0-based format (add 1 to compare with R output)",
354
- gene_name, index
355
- );
356
- index
357
- }
358
- None => {
359
- println!(
360
- "{}",
361
- serde_json::json!({
362
- "status": "failure",
363
- "message": format!("Gene '{}' not found in the HDF5 file '{}'", gene_name, &hdf5_filename),
364
- "file_path": hdf5_filename,
365
- "gene": gene_name
366
- })
367
- );
368
- return Ok(());
369
- }
370
- };
371
-
372
- // Find the number of columns that are populated for that gene
373
- let now_p = Instant::now();
374
- let ds_p = file.dataset("data/p")?;
375
- let data_partial_p: Array1<usize> = ds_p.read_slice_1d(gene_index..gene_index + 2)?;
376
- println!("Data_partial_p: {:?}", data_partial_p);
377
- println!("Time for p dataset:{:?}", now_p.elapsed());
378
-
379
- let array_start_point = data_partial_p[0];
380
- let array_stop_point = data_partial_p[1];
381
- let num_populated_cells = array_stop_point - array_start_point;
382
- println!("Number of populated cells:{}", num_populated_cells);
383
-
384
- // Find all columns indices that are populated for the given gene
385
- let now_i = Instant::now();
386
- let ds_i = file.dataset("data/i")?;
387
- let populated_column_ids: Array1<usize> = ds_i.read_slice_1d(array_start_point..array_stop_point)?;
388
- println!("Time for i dataset:{:?}", now_i.elapsed());
389
-
390
- // Find all columns values that are populated for the given gene
391
- let now_x = Instant::now();
392
- let ds_x = file.dataset("data/x")?;
393
- let populated_column_values: Array1<f64> = ds_x.read_slice_1d(array_start_point..array_stop_point)?;
394
- println!("Time for x dataset:{:?}", now_x.elapsed());
395
-
396
- // Generate the complete array from the sparse array
397
- let mut gene_array: Array1<f64> = Array1::zeros(num_samples);
398
- let time_generating_full_array = Instant::now();
399
-
400
- // Fill in the values at the populated column indices
401
- for (idx, &col_id) in populated_column_ids.iter().enumerate() {
402
- gene_array[col_id] = populated_column_values[idx];
403
- }
404
-
405
- let mut output_string = "{".to_string();
406
- for i in 0..gene_array.len() {
407
- output_string += &format!(
408
- "\"{}\":{}",
409
- samples[i].to_string().replace("\\", ""),
410
- gene_array[i].to_string()
411
- );
412
-
413
- if i != gene_array.len() - 1 {
414
- output_string += &",";
415
- }
416
- }
417
- output_string += &"}".to_string();
418
-
419
- println!("Time generating full array:{:?}", time_generating_full_array.elapsed());
420
- println!("output_string:{}", output_string);
421
-
422
- Ok(())
423
- }
424
-
425
- /// Queries expression data for multiple genes from a dense format HDF5 file
426
- ///
427
- /// Extracts expression values for multiple genes from a dense matrix HDF5 file,
428
- /// optimizing for both single gene (linear search) and multi-gene (hashmap) queries.
429
- ///
430
- /// # Arguments
431
- /// * `hdf5_filename` - Path to the HDF5 file
432
- /// * `gene_names` - Vector of gene names to query
433
- ///
434
- /// # Returns
435
- /// Prints a JSON object with expression data for all requested genes to stdout.
436
- fn query_multiple_genes_dense(hdf5_filename: String, gene_names: Vec<String>) -> Result<()> {
437
- let overall_start_time = Instant::now();
438
-
439
- // Create timing map to store all timing data
440
- let mut timings = Map::new();
441
-
442
- let file = match File::open(&hdf5_filename) {
443
- Ok(f) => f,
444
- Err(err) => {
445
- println!(
446
- "{}",
447
- serde_json::json!({
448
- "status": "error",
449
- "message": format!("Failed to open HDF5 file: {}", err)
450
- })
451
- );
452
- return Ok(());
453
- }
454
- };
455
-
456
- let genes_dataset = match file.dataset("gene_ids") {
457
- Ok(ds) => ds,
458
- Err(err) => {
459
- println!(
460
- "{}",
461
- serde_json::json!({
462
- "status": "error",
463
- "message": format!("Failed to open gene_ids dataset: {}", err)
464
- })
465
- );
466
- return Ok(());
467
- }
468
- };
469
-
470
- let genes_varlen = match genes_dataset.read_1d::<VarLenAscii>() {
471
- Ok(g) => g,
472
- Err(err) => {
473
- println!(
474
- "{}",
475
- serde_json::json!({
476
- "status": "error",
477
- "message": format!("Failed to read gene names as VarLenAscii: {}", err)
478
- })
479
- );
480
- return Ok(());
481
- }
482
- };
483
-
484
- let genes: Vec<String> = genes_varlen.iter().map(|g| g.to_string()).collect();
485
-
486
- // Only create HashMap for multiple gene queries
487
- let gene_to_index: Option<std::collections::HashMap<String, usize>> = if gene_names.len() > 1 {
488
- let hashmap_start_time = Instant::now();
489
- let mut map = std::collections::HashMap::with_capacity(genes.len());
490
- for (idx, gene) in genes.iter().enumerate() {
491
- map.insert(gene.clone(), idx);
492
- }
493
- timings.insert(
494
- "build_hashmap_ms".to_string(),
495
- Value::from(hashmap_start_time.elapsed().as_millis() as u64),
496
- );
497
- Some(map)
498
- } else {
499
- // Skip HashMap creation for single gene queries
500
- None
501
- };
502
-
503
- let samples_dataset = match file.dataset("samples") {
504
- Ok(ds) => ds,
505
- Err(err) => {
506
- println!(
507
- "{}",
508
- serde_json::json!({
509
- "status": "error",
510
- "message": format!("Failed to open samples dataset: {}", err)
511
- })
512
- );
513
- return Ok(());
514
- }
515
- };
516
-
517
- let samples_varlen = match samples_dataset.read_1d::<VarLenAscii>() {
518
- Ok(s) => s,
519
- Err(err) => {
520
- println!(
521
- "{}",
522
- serde_json::json!({
523
- "status": "error",
524
- "message": format!("Failed to read samples as VarLenAscii: {}", err)
525
- })
526
- );
527
- return Ok(());
528
- }
529
- };
530
-
531
- let samples: Vec<String> = samples_varlen.iter().map(|s| s.to_string()).collect();
532
-
533
- let counts_dataset = match file.dataset("counts") {
534
- Ok(ds) => ds,
535
- Err(err) => {
536
- println!(
537
- "{}",
538
- serde_json::json!({
539
- "status": "error",
540
- "message": format!("Failed to open counts dataset: {}", err)
541
- })
542
- );
543
- return Ok(());
544
- }
545
- };
546
-
547
- // Create thread-local storage for results
548
- let genes_map = Arc::new(std::sync::Mutex::new(Map::new()));
549
- let gene_timings = Arc::new(std::sync::Mutex::new(Map::new()));
550
-
551
- if gene_names.len() > 1 {
552
- // For multiple genes: preload all data and use parallel processing
553
- timings.insert("parallel_processing".to_string(), Value::from(true));
554
-
555
- // Load all gene data upfront only when processing multiple genes
556
- let all_data_start_time = Instant::now();
557
- let all_gene_data = match counts_dataset.read::<f64, Dim<[usize; 2]>>() {
558
- Ok(data) => {
559
- timings.insert(
560
- "read_all_gene_data_ms".to_string(),
561
- Value::from(all_data_start_time.elapsed().as_millis() as u64),
562
- );
563
- Some(data)
564
- }
565
- Err(err) => {
566
- // Failed to read all data at once, will fallback to per-gene reading
567
- timings.insert(
568
- "read_all_gene_data_error".to_string(),
569
- Value::String(format!("{:?}", err)),
570
- );
571
- None
572
- }
573
- };
574
-
575
- // Configurable thread count for testing
576
- let thread_count = 2;
577
- timings.insert("thread_count".to_string(), Value::from(thread_count));
578
-
579
- // Create a scoped thread pool with specified number of threads
580
- match rayon::ThreadPoolBuilder::new().num_threads(thread_count).build() {
581
- Ok(pool) => {
582
- // Use the pool for this specific work
583
- pool.install(|| {
584
- gene_names.par_iter().for_each(|gene_name| {
585
- let gene_start_time = Instant::now();
586
-
587
- // Use HashMap for O(1) lookup for multiple genes
588
- let gene_index = match &gene_to_index {
589
- Some(map) => map.get(gene_name).cloned(),
590
- None => genes.iter().position(|x| *x == *gene_name),
591
- };
592
-
593
- match gene_index {
594
- Some(gene_index) => {
595
- // Make sure the gene index is valid for this dataset
596
- if gene_index >= counts_dataset.shape()[0] {
597
- let mut error_map = Map::new();
598
- error_map.insert(
599
- "error".to_string(),
600
- Value::String("Gene index out of bounds".to_string()),
601
- );
602
-
603
- // Store the error result
604
- let mut genes_map = genes_map.lock().unwrap();
605
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
606
- } else {
607
- // Use pre-loaded data if available
608
- if let Some(ref all_data) = all_gene_data {
609
- // Extract the row directly from pre-loaded data
610
- let gene_expression = all_data.slice(s![gene_index, ..]);
611
-
612
- // Create samples map for this gene
613
- let mut samples_map = Map::new();
614
- for (i, sample) in samples.iter().enumerate() {
615
- if i < gene_expression.len() {
616
- // Handle potential NaN or infinity values
617
- let value = if gene_expression[i].is_finite() {
618
- Value::from(gene_expression[i])
619
- } else {
620
- Value::Null
621
- };
622
-
623
- samples_map.insert(sample.replace("\\", ""), value);
624
- }
625
- }
626
-
627
- // Create gene data and store it
628
- let gene_data = json!({
629
- "dataId": gene_name,
630
- "samples": samples_map
631
- });
632
-
633
- let mut genes_map = genes_map.lock().unwrap();
634
- genes_map.insert(gene_name.clone(), gene_data);
635
- } else {
636
- // Fallback to per-gene reading if bulk load failed
637
- match counts_dataset.read_slice_1d::<f64, _>(s![gene_index, ..]) {
638
- Ok(gene_expression) => {
639
- // Create samples map for this gene
640
- let mut samples_map = Map::new();
641
- for (i, sample) in samples.iter().enumerate() {
642
- if i < gene_expression.len() {
643
- // Handle potential NaN or infinity values
644
- let value = if gene_expression[i].is_finite() {
645
- Value::from(gene_expression[i])
646
- } else {
647
- Value::Null
648
- };
649
-
650
- samples_map.insert(sample.replace("\\", ""), value);
651
- }
652
- }
653
-
654
- // Create gene data and store it
655
- let gene_data = json!({
656
- "dataId": gene_name,
657
- "samples": samples_map
658
- });
659
-
660
- let mut genes_map = genes_map.lock().unwrap();
661
- genes_map.insert(gene_name.clone(), gene_data);
662
- }
663
- Err(err1) => {
664
- let mut error_map = Map::new();
665
- error_map.insert(
666
- "error".to_string(),
667
- Value::String(format!(
668
- "Failed to read expression values: {:?}",
669
- err1
670
- )),
671
- );
672
-
673
- let mut genes_map = genes_map.lock().unwrap();
674
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
675
- }
676
- }
677
- }
678
- }
679
- }
680
- None => {
681
- // Gene not found
682
- let mut error_map = Map::new();
683
- error_map.insert(
684
- "error".to_string(),
685
- Value::String("Gene not found in dataset".to_string()),
686
- );
687
-
688
- let mut genes_map = genes_map.lock().unwrap();
689
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
690
- }
691
- }
692
-
693
- // Record timing
694
- let elapsed_time = gene_start_time.elapsed().as_millis() as u64;
695
- let mut gene_timings = gene_timings.lock().unwrap();
696
- gene_timings.insert(gene_name.clone(), Value::from(elapsed_time));
697
- });
698
- });
699
- }
700
- Err(err) => {
701
- // If thread pool creation fails, fall back to sequential processing
702
- timings.insert(
703
- "thread_pool_error".to_string(),
704
- Value::String(format!("Failed to create thread pool: {:?}", err)),
705
- );
706
-
707
- process_genes_sequentially(
708
- &gene_names,
709
- &genes,
710
- &gene_to_index,
711
- &counts_dataset,
712
- &all_gene_data,
713
- &samples,
714
- &genes_map,
715
- );
716
- }
717
- }
718
- } else if gene_names.len() == 1 {
719
- let gene_name = &gene_names[0];
720
-
721
- match genes.iter().position(|x| *x == *gene_name) {
722
- Some(gene_index) => {
723
- if gene_index >= counts_dataset.shape()[0] {
724
- let mut error_map = Map::new();
725
- error_map.insert(
726
- "error".to_string(),
727
- Value::String("Gene index out of bounds".to_string()),
728
- );
729
-
730
- let mut genes_map = genes_map.lock().unwrap();
731
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
732
- } else {
733
- // Read just this single gene's data directly
734
- match counts_dataset.read_slice_1d::<f64, _>(s![gene_index, ..]) {
735
- Ok(gene_expression) => {
736
- // Create samples map for this gene
737
- let mut samples_map = Map::new();
738
- for (i, sample) in samples.iter().enumerate() {
739
- if i < gene_expression.len() {
740
- // Handle potential NaN or infinity values
741
- let value = if gene_expression[i].is_finite() {
742
- Value::from(gene_expression[i])
743
- } else {
744
- Value::Null
745
- };
746
-
747
- samples_map.insert(sample.replace("\\", ""), value);
748
- }
749
- }
750
-
751
- let gene_data = json!({
752
- "dataId": gene_name,
753
- "samples": samples_map
754
- });
755
-
756
- let mut genes_map = genes_map.lock().unwrap();
757
- genes_map.insert(gene_name.clone(), gene_data);
758
- }
759
- Err(err) => {
760
- let mut error_map = Map::new();
761
- error_map.insert(
762
- "error".to_string(),
763
- Value::String(format!("Failed to read expression values: {:?}", err)),
764
- );
765
-
766
- let mut genes_map = genes_map.lock().unwrap();
767
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
768
- }
769
- }
770
- }
771
- }
772
- None => {
773
- let mut error_map = Map::new();
774
- error_map.insert(
775
- "error".to_string(),
776
- Value::String("Gene not found in dataset".to_string()),
777
- );
778
-
779
- let mut genes_map = genes_map.lock().unwrap();
780
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
781
- }
782
- }
783
- }
784
-
785
- // Get the final maps from the Arc<Mutex<>>
786
- let genes_map = Arc::try_unwrap(genes_map).unwrap().into_inner().unwrap();
787
-
788
- let output_json = json!({
789
- "genes": genes_map,
790
- "timings": timings,
791
- "total_time_ms": overall_start_time.elapsed().as_millis() as u64
792
- });
793
-
794
- println!("{}", output_json);
795
-
796
- Ok(())
797
- }
798
-
799
- // Helper function to process genes sequentially with optional HashMap lookup
800
- fn process_genes_sequentially(
801
- gene_names: &Vec<String>,
802
- genes: &Vec<String>,
803
- gene_to_index: &Option<std::collections::HashMap<String, usize>>,
804
- counts_dataset: &hdf5::Dataset,
805
- all_gene_data: &Option<ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>>,
806
- samples: &Vec<String>,
807
- genes_map: &Arc<std::sync::Mutex<Map<String, Value>>>,
808
- ) {
809
- for gene_name in gene_names {
810
- // Find the index of the requested gene, using HashMap if available
811
- let gene_index = match gene_to_index {
812
- Some(map) => map.get(gene_name).cloned(),
813
- None => genes.iter().position(|x| *x == *gene_name),
814
- };
815
-
816
- match gene_index {
817
- Some(gene_index) => {
818
- // Make sure the gene index is valid for this dataset
819
- if gene_index >= counts_dataset.shape()[0] {
820
- let mut error_map = Map::new();
821
- error_map.insert(
822
- "error".to_string(),
823
- Value::String("Gene index out of bounds".to_string()),
824
- );
825
-
826
- // Store the error result
827
- let mut genes_map = genes_map.lock().unwrap();
828
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
829
- } else {
830
- // Use pre-loaded data if available
831
- if let Some(ref all_data) = all_gene_data {
832
- let gene_expression = all_data.slice(s![gene_index, ..]);
833
-
834
- // Create samples map for this gene
835
- let mut samples_map = Map::new();
836
- for (i, sample) in samples.iter().enumerate() {
837
- if i < gene_expression.len() {
838
- let value = if gene_expression[i].is_finite() {
839
- Value::from(gene_expression[i])
840
- } else {
841
- Value::Null
842
- };
843
-
844
- samples_map.insert(sample.replace("\\", ""), value);
845
- }
846
- }
847
-
848
- let gene_data = json!({
849
- "dataId": gene_name,
850
- "samples": samples_map
851
- });
852
-
853
- let mut genes_map = genes_map.lock().unwrap();
854
- genes_map.insert(gene_name.clone(), gene_data);
855
- } else {
856
- // Fallback to per-gene reading if bulk load failed
857
- match counts_dataset.read_slice_1d::<f64, _>(s![gene_index, ..]) {
858
- Ok(gene_expression) => {
859
- // Create samples map for this gene
860
- let mut samples_map = Map::new();
861
- for (i, sample) in samples.iter().enumerate() {
862
- if i < gene_expression.len() {
863
- let value = if gene_expression[i].is_finite() {
864
- Value::from(gene_expression[i])
865
- } else {
866
- Value::Null
867
- };
868
-
869
- samples_map.insert(sample.replace("\\", ""), value);
870
- }
871
- }
872
-
873
- let gene_data = json!({
874
- "dataId": gene_name,
875
- "samples": samples_map
876
- });
877
-
878
- let mut genes_map = genes_map.lock().unwrap();
879
- genes_map.insert(gene_name.clone(), gene_data);
880
- }
881
- Err(err1) => {
882
- let mut error_map = Map::new();
883
- error_map.insert(
884
- "error".to_string(),
885
- Value::String(format!("Failed to read expression values: {:?}", err1)),
886
- );
887
-
888
- let mut genes_map = genes_map.lock().unwrap();
889
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
890
- }
891
- }
892
- }
893
- }
894
- }
895
- None => {
896
- let mut error_map = Map::new();
897
- error_map.insert(
898
- "error".to_string(),
899
- Value::String("Gene not found in dataset".to_string()),
900
- );
901
-
902
- let mut genes_map = genes_map.lock().unwrap();
903
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
904
- }
905
- }
906
- }
907
- }
908
- /// Queries expression data for multiple genes from a sparse format HDF5 file
909
- ///
910
- /// This function extracts expression values for multiple specified genes from an HDF5 file
911
- /// that uses a sparse matrix representation. It optimizes the query by reading shared datasets only once.
912
- ///
913
- /// # Arguments
914
- ///
915
- /// * `hdf5_filename` - Path to the HDF5 file
916
- /// * `gene_names` - Vector of gene names to query
917
- ///
918
- /// # Returns
919
- ///
920
- /// A result indicating success or error. On success, the function prints a JSON object
921
- /// containing expression data for all requested genes to stdout.
922
- fn query_multiple_genes_sparse(hdf5_filename: String, gene_names: Vec<String>) -> Result<()> {
923
- let overall_start_time = Instant::now();
924
-
925
- // Create timing map
926
- let mut timings = Map::new();
927
- timings.insert("gene_count".to_string(), Value::from(gene_names.len()));
928
- timings.insert("format".to_string(), Value::String("sparse".to_string()));
929
-
930
- // Open file and read datasets
931
- let file_open_start = Instant::now();
932
- let file = File::open(&hdf5_filename)?;
933
- timings.insert(
934
- "file_open_ms".to_string(),
935
- Value::from(file_open_start.elapsed().as_millis() as u64),
936
- );
937
-
938
- let dim_start = Instant::now();
939
- let ds_dim = file.dataset("data/dim")?;
940
- let data_dim: Array1<_> = ds_dim.read::<usize, Dim<[usize; 1]>>()?;
941
- let num_samples = data_dim[0];
942
- let _num_genes = data_dim[1];
943
- timings.insert(
944
- "read_dims_ms".to_string(),
945
- Value::from(dim_start.elapsed().as_millis() as u64),
946
- );
947
-
948
- let ds_genes = file.dataset("gene_names")?;
949
- let genes = ds_genes.read_1d::<FixedAscii<104>>()?;
950
-
951
- let ds_samples = file.dataset("sample_names")?;
952
- let samples = ds_samples.read_1d::<FixedAscii<104>>()?;
953
-
954
- // Read p dataset (contains pointers for all genes)
955
- let p_start_time = Instant::now();
956
- let ds_p = file.dataset("data/p")?;
957
- let data_p: Array1<usize> = ds_p.read_1d::<usize>()?;
958
- timings.insert(
959
- "read_p_dataset_ms".to_string(),
960
- Value::from(p_start_time.elapsed().as_millis() as u64),
961
- );
962
-
963
- // Open i and x datasets
964
- let ds_start_time = Instant::now();
965
- let ds_i = file.dataset("data/i")?;
966
- let ds_x = file.dataset("data/x")?;
967
- timings.insert(
968
- "open_i_x_datasets_ms".to_string(),
969
- Value::from(ds_start_time.elapsed().as_millis() as u64),
970
- );
971
-
972
- // Determine number of threads to use
973
- let num_threads = num_cpus::get();
974
- timings.insert("num_threads".to_string(), Value::from(num_threads as u64));
975
-
976
- // Thread-safe maps for results
977
- let genes_map = Arc::new(std::sync::Mutex::new(Map::new()));
978
- let gene_timings = Arc::new(std::sync::Mutex::new(Map::new()));
979
-
980
- // Use rayon for parallel processing
981
- gene_names.par_iter().for_each(|gene_name| {
982
- let gene_start_time = Instant::now();
983
-
984
- // Find the index of the requested gene
985
- match genes.iter().position(|&x| x == *gene_name) {
986
- Some(gene_index) => {
987
- // Find start and end points for this gene's data
988
- let array_start_point = data_p[gene_index];
989
- let array_stop_point = data_p[gene_index + 1];
990
- let num_populated_cells = array_stop_point - array_start_point;
991
-
992
- if num_populated_cells == 0 {
993
- // Gene has no data, create array of zeros
994
- let mut samples_map = Map::new();
995
- for (_i, sample) in samples.iter().enumerate() {
996
- samples_map.insert(sample.to_string().replace("\\", ""), Value::from(0.0));
997
- }
998
-
999
- let gene_data = json!({
1000
- "dataId": gene_name,
1001
- "samples": samples_map
1002
- });
1003
-
1004
- let mut genes_map = genes_map.lock().unwrap();
1005
- genes_map.insert(gene_name.clone(), gene_data);
1006
- } else {
1007
- // Read data for this gene
1008
- match ds_i.read_slice_1d::<usize, _>(array_start_point..array_stop_point) {
1009
- Ok(populated_column_ids) => {
1010
- match ds_x.read_slice_1d::<f64, _>(array_start_point..array_stop_point) {
1011
- Ok(populated_column_values) => {
1012
- // Generate the complete array from sparse representation
1013
- let mut gene_array: Array1<f64> = Array1::zeros(num_samples);
1014
-
1015
- // Fill in values at populated column indices
1016
- for (idx, &col_id) in populated_column_ids.iter().enumerate() {
1017
- gene_array[col_id] = populated_column_values[idx];
1018
- }
1019
-
1020
- // Create samples map
1021
- let mut samples_map = Map::new();
1022
- for (_i, sample) in samples.iter().enumerate() {
1023
- let value = if gene_array[_i].is_finite() {
1024
- Value::from(gene_array[_i])
1025
- } else {
1026
- Value::Null
1027
- };
1028
-
1029
- samples_map.insert(sample.to_string().replace("\\", ""), value);
1030
- }
1031
-
1032
- let gene_data = json!({
1033
- "dataId": gene_name,
1034
- "samples": samples_map
1035
- });
1036
-
1037
- let mut genes_map = genes_map.lock().unwrap();
1038
- genes_map.insert(gene_name.clone(), gene_data);
1039
- }
1040
- Err(err) => {
1041
- let mut error_map = Map::new();
1042
- error_map.insert(
1043
- "error".to_string(),
1044
- Value::String(format!("Failed to read x dataset: {:?}", err)),
1045
- );
1046
-
1047
- let mut genes_map = genes_map.lock().unwrap();
1048
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
1049
- }
1050
- }
1051
- }
1052
- Err(err) => {
1053
- let mut error_map = Map::new();
1054
- error_map.insert(
1055
- "error".to_string(),
1056
- Value::String(format!("Failed to read i dataset: {:?}", err)),
1057
- );
1058
-
1059
- let mut genes_map = genes_map.lock().unwrap();
1060
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
1061
- }
1062
- }
1063
- }
1064
- }
1065
- None => {
1066
- let mut error_map = Map::new();
1067
- error_map.insert(
1068
- "error".to_string(),
1069
- Value::String("Gene not found in dataset".to_string()),
1070
- );
1071
-
1072
- let mut genes_map = genes_map.lock().unwrap();
1073
- genes_map.insert(gene_name.clone(), Value::Object(error_map));
1074
- }
1075
- }
1076
-
1077
- // Record timing
1078
- let elapsed_time = gene_start_time.elapsed().as_millis() as u64;
1079
- let mut gene_timings = gene_timings.lock().unwrap();
1080
- gene_timings.insert(gene_name.clone(), Value::from(elapsed_time));
1081
- });
1082
-
1083
- // Get the final maps from the Arc<Mutex<>>
1084
- let genes_map = Arc::try_unwrap(genes_map).unwrap().into_inner().unwrap();
1085
-
1086
- let output_json = json!({
1087
- "genes": genes_map,
1088
- "timings": timings,
1089
- "parallel": true,
1090
- "total_time_ms": overall_start_time.elapsed().as_millis() as u64
1091
- });
1092
-
1093
- println!("{}", output_json);
1094
-
1095
- Ok(())
1096
- }
1097
- fn main() -> Result<()> {
1098
- let mut input = String::new();
1099
- match io::stdin().read_line(&mut input) {
1100
- Ok(_bytes_read) => {
1101
- let input_json = json::parse(&input);
1102
- match input_json {
1103
- Ok(json_string) => {
1104
- // Extract HDF5 filename
1105
- let hdf5_filename = match json_string["hdf5_file"].as_str() {
1106
- Some(x) => x.to_string(),
1107
- None => {
1108
- panic!("HDF5 filename not provided");
1109
- }
1110
- };
1111
-
1112
- // Case 1: Check if "genes" field exists and is an array
1113
- if json_string["genes"].is_array() {
1114
- // Convert the JsonValue array to a Vec<String>
1115
- let mut gene_names: Vec<String> = Vec::new();
1116
- for gene_value in json_string["genes"].members() {
1117
- if let Some(gene_str) = gene_value.as_str() {
1118
- gene_names.push(gene_str.to_string());
1119
- }
1120
- }
1121
-
1122
- if !gene_names.is_empty() {
1123
- match detect_hdf5_format(&hdf5_filename)? {
1124
- "dense" => query_multiple_genes_dense(hdf5_filename, gene_names)?,
1125
- "sparse" => query_multiple_genes_sparse(hdf5_filename, gene_names)?,
1126
- _ => {
1127
- println!(
1128
- "{}",
1129
- serde_json::json!({
1130
- "status": "failure",
1131
- "message": "Cannot query genes in unknown file format.",
1132
- "file_path": hdf5_filename
1133
- })
1134
- );
1135
- }
1136
- }
1137
- return Ok(());
1138
- }
1139
- }
1140
- // Case 2: Check if "gene" field exists and is an array (this handles the case we're seeing)
1141
- else if json_string["gene"].is_array() {
1142
- // Convert the JsonValue array to a Vec<String>
1143
- let mut gene_names: Vec<String> = Vec::new();
1144
- for gene_value in json_string["gene"].members() {
1145
- if let Some(gene_str) = gene_value.as_str() {
1146
- gene_names.push(gene_str.to_string());
1147
- }
1148
- }
1149
-
1150
- if !gene_names.is_empty() {
1151
- // Process multiple genes
1152
- match detect_hdf5_format(&hdf5_filename)? {
1153
- "dense" => query_multiple_genes_dense(hdf5_filename, gene_names)?,
1154
- "sparse" => query_multiple_genes_sparse(hdf5_filename, gene_names)?,
1155
- _ => {
1156
- println!(
1157
- "{}",
1158
- serde_json::json!({
1159
- "status": "failure",
1160
- "message": "Cannot query genes in unknown file format.",
1161
- "file_path": hdf5_filename
1162
- })
1163
- );
1164
- }
1165
- }
1166
- return Ok(());
1167
- }
1168
- }
1169
- // Case 3: Check if "gene" field exists and is a string (original single gene case)
1170
- else if let Some(gene_name) = json_string["gene"].as_str() {
1171
- query_gene(hdf5_filename, gene_name.to_string())?;
1172
- return Ok(());
1173
- }
1174
- println!(
1175
- "{}",
1176
- serde_json::json!({
1177
- "status": "error",
1178
- "message": "Neither gene nor genes array provided in input"
1179
- })
1180
- );
1181
- }
1182
- Err(error) => println!("Incorrect json: {}", error),
1183
- }
1184
- }
1185
- Err(error) => println!("Piping error: {}", error),
1186
- }
1187
- Ok(())
1188
- }