M3Drop 0.4.58__tar.gz → 0.4.60__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: M3Drop
3
- Version: 0.4.58
3
+ Version: 0.4.60
4
4
  Summary: A Python implementation of the M3Drop single-cell RNA-seq analysis tool.
5
5
  Home-page: https://github.com/PragalvhaSharma/m3DropNew
6
6
  Author: Tallulah Andrews
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: M3Drop
3
- Version: 0.4.58
3
+ Version: 0.4.60
4
4
  Summary: A Python implementation of the M3Drop single-cell RNA-seq analysis tool.
5
5
  Home-page: https://github.com/PragalvhaSharma/m3DropNew
6
6
  Author: Tallulah Andrews
@@ -22,10 +22,19 @@ from scipy.stats import norm
22
22
  from scipy import sparse
23
23
  from statsmodels.stats.multitest import multipletests
24
24
 
25
- # [FIX] Strict Relative Import
26
- # This ensures that if ControlDeviceCPU fails to load (e.g. missing dependency),
27
- # the real error is shown instead of being masked.
28
- from .ControlDeviceCPU import ControlDevice
25
+ # ==========================================
26
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
27
+ # ==========================================
28
+ try:
29
+ # Case 1: Running as an installed package
30
+ from .ControlDeviceCPU import ControlDevice
31
+ except ImportError:
32
+ # Case 2: Running locally
33
+ try:
34
+ from ControlDeviceCPU import ControlDevice
35
+ except ImportError:
36
+ print("CRITICAL ERROR: 'ControlDeviceCPU.py' not found.")
37
+ sys.exit(1)
29
38
 
30
39
  # ==========================================
31
40
  # NUMBA KERNELS (CPU OPTIMIZED)
@@ -323,9 +332,17 @@ def NBumiFitDispVsMeanCPU(fit: dict, suppress_plot=True):
323
332
  tjs = vals['tjs'].values
324
333
  mean_expression = tjs / vals['nc']
325
334
 
326
- forfit = (np.isfinite(size_g)) & (size_g < 1e6) & (mean_expression > 1e-3) & (size_g > 0)
335
+ # [FIX] Filter out the 10,000 imputation values.
336
+ # We treat 10,000 as an error code for "Under-Dispersed/Poissonian".
337
+ # Masking these prevents artificial gravity from pulling the regression line up.
338
+ forfit = (np.isfinite(size_g)) & \
339
+ (size_g < 9999.0) & \
340
+ (mean_expression > 1e-3) & \
341
+ (size_g > 0)
342
+
327
343
  log2_mean_expr = np.log2(mean_expression, where=(mean_expression > 0))
328
344
 
345
+ # Heuristic: If we have enough high-expression genes, focus fit there
329
346
  higher = log2_mean_expr > 4
330
347
  if np.sum(higher & forfit) > 2000:
331
348
  forfit = higher & forfit
@@ -338,8 +355,10 @@ def NBumiFitDispVsMeanCPU(fit: dict, suppress_plot=True):
338
355
 
339
356
  if not suppress_plot:
340
357
  plt.figure(figsize=(7, 6))
341
- plt.scatter(x, y, alpha=0.5, s=1)
342
- plt.plot(x, model.fittedvalues, color='red')
358
+ # Plot ALL points (grey) vs FITTED points (blue)
359
+ plt.scatter(np.log(mean_expression), np.log(size_g), alpha=0.3, s=1, c='grey', label='All Genes')
360
+ plt.scatter(x, y, alpha=0.5, s=1, c='blue', label='Used for Fit')
361
+ plt.plot(x, model.fittedvalues, color='red', label='Regression Fit')
343
362
  plt.show()
344
363
 
345
364
  return model.params
@@ -22,8 +22,19 @@ import matplotlib.pyplot as plt
22
22
  from scipy.stats import norm
23
23
  from statsmodels.stats.multitest import multipletests
24
24
 
25
- # Package-compatible import
26
- from .ControlDeviceGPU import ControlDevice
25
+ # ==========================================
26
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
27
+ # ==========================================
28
+ try:
29
+ # Case 1: Running as an installed package (e.g. import m3drop.CoreGPU)
30
+ from .ControlDeviceGPU import ControlDevice
31
+ except ImportError:
32
+ # Case 2: Running locally (e.g. python CoreGPU.py)
33
+ try:
34
+ from ControlDeviceGPU import ControlDevice
35
+ except ImportError:
36
+ print("CRITICAL ERROR: 'ControlDeviceGPU.py' not found.")
37
+ sys.exit(1)
27
38
 
28
39
  # ==========================================
29
40
  # FUSED KERNELS
@@ -313,9 +324,14 @@ def NBumiFitDispVsMeanGPU(fit: dict, suppress_plot=True):
313
324
  tjs = vals['tjs'].values
314
325
  mean_expression = tjs / vals['nc']
315
326
 
316
- forfit = (np.isfinite(size_g)) & (size_g < 1e6) & (mean_expression > 1e-3) & (size_g > 0)
327
+ forfit = (np.isfinite(size_g)) & \
328
+ (size_g < 9999.0) & \
329
+ (mean_expression > 1e-3) & \
330
+ (size_g > 0)
331
+
317
332
  log2_mean_expr = np.log2(mean_expression, where=(mean_expression > 0))
318
333
 
334
+ # Heuristic: If we have enough high-expression genes, focus fit there
319
335
  higher = log2_mean_expr > 4
320
336
  if np.sum(higher & forfit) > 2000:
321
337
  forfit = higher & forfit
@@ -328,7 +344,8 @@ def NBumiFitDispVsMeanGPU(fit: dict, suppress_plot=True):
328
344
 
329
345
  if not suppress_plot:
330
346
  plt.figure(figsize=(7, 6))
331
- plt.scatter(x, y, alpha=0.5, s=1)
347
+ # Visual check only - code below handles the production plot
348
+ plt.scatter(x, y, alpha=0.5, s=1, c='blue')
332
349
  plt.plot(x, model.fittedvalues, color='red')
333
350
  plt.show()
334
351
 
@@ -4,6 +4,7 @@ import matplotlib.pyplot as plt
4
4
  import h5py
5
5
  import os
6
6
  import time
7
+ import sys
7
8
  import pickle
8
9
  import gc
9
10
  from scipy import sparse
@@ -14,9 +15,29 @@ import statsmodels.api as sm
14
15
  from scipy.stats import norm
15
16
  from statsmodels.stats.multitest import multipletests
16
17
 
17
- # [FIX] Strict Relative Imports
18
- from .ControlDeviceCPU import ControlDevice
19
- from .CoreCPU import hidden_calc_valsCPU, NBumiFitModelCPU, NBumiFitDispVsMeanCPU, dropout_prob_kernel_cpu
18
+ # ==========================================
19
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
20
+ # ==========================================
21
+
22
+ # [FIX] Hybrid Import: ControlDeviceCPU
23
+ try:
24
+ from .ControlDeviceCPU import ControlDevice
25
+ except ImportError:
26
+ try:
27
+ from ControlDeviceCPU import ControlDevice
28
+ except ImportError:
29
+ print("CRITICAL ERROR: 'ControlDeviceCPU.py' not found.")
30
+ sys.exit(1)
31
+
32
+ # [FIX] Hybrid Import: CoreCPU
33
+ try:
34
+ from .CoreCPU import hidden_calc_valsCPU, NBumiFitModelCPU, NBumiFitDispVsMeanCPU, dropout_prob_kernel_cpu
35
+ except ImportError:
36
+ try:
37
+ from CoreCPU import hidden_calc_valsCPU, NBumiFitModelCPU, NBumiFitDispVsMeanCPU, dropout_prob_kernel_cpu
38
+ except ImportError:
39
+ print("CRITICAL ERROR: 'CoreCPU.py' not found.")
40
+ sys.exit(1)
20
41
 
21
42
  # ==========================================
22
43
  # DIAGNOSTICS & COMPARISON (CPU)
@@ -244,7 +265,7 @@ def NBumiCompareModelsCPU(
244
265
  stats: dict,
245
266
  fit_adjust: dict,
246
267
  mask_filename: str = None,
247
- mode: str = "auto",
268
+ mode: str = "auto",
248
269
  manual_target: int = 3000,
249
270
  suppress_plot=False,
250
271
  plot_filename=None
@@ -365,9 +386,11 @@ def NBumiPlotDispVsMeanCPU(
365
386
  mean_expression = fit['vals']['tjs'].values / fit['vals']['nc']
366
387
  sizes = fit['sizes'].values
367
388
 
389
+ # 1. Get calibrated coefficients (using the fixed CoreCPU logic)
368
390
  coeffs = NBumiFitDispVsMeanCPU(fit, suppress_plot=True)
369
391
  intercept, slope = coeffs[0], coeffs[1]
370
392
 
393
+ # 2. Calculate the regression line
371
394
  log_mean_expr_range = np.linspace(
372
395
  np.log(mean_expression[mean_expression > 0].min()),
373
396
  np.log(mean_expression.max()),
@@ -376,8 +399,19 @@ def NBumiPlotDispVsMeanCPU(
376
399
  log_fitted_sizes = intercept + slope * log_mean_expr_range
377
400
  fitted_sizes = np.exp(log_fitted_sizes)
378
401
 
402
+ # 3. [FIX] Mask the 10k outliers for the SCATTER PLOT
403
+ # We create a visualization mask to hide the distracting "roof" at y=10000
404
+ mask_viz = (sizes < 9999.0) & (sizes > 0)
405
+
406
+ mean_expr_clean = mean_expression[mask_viz]
407
+ sizes_clean = sizes[mask_viz]
408
+
379
409
  plt.figure(figsize=(8, 6))
380
- plt.scatter(mean_expression, sizes, label='Observed Dispersion', alpha=0.5, s=8)
410
+
411
+ # Plot only the clean data
412
+ plt.scatter(mean_expr_clean, sizes_clean, label='Observed Dispersion', alpha=0.5, s=8)
413
+
414
+ # Plot the regression line (calculated correctly via CoreCPU)
381
415
  plt.plot(np.exp(log_mean_expr_range), fitted_sizes, color='red', label='Regression Fit', linewidth=2)
382
416
 
383
417
  plt.xscale('log')
@@ -9,17 +9,36 @@ import time
9
9
  import pickle
10
10
  import psutil
11
11
  import gc
12
+ import sys
12
13
  from scipy import sparse
13
14
  from scipy import stats
14
15
  import anndata
15
16
 
16
- from .ControlDeviceGPU import ControlDevice
17
- from .CoreGPU import (
18
- hidden_calc_valsGPU,
19
- NBumiFitModelGPU,
20
- NBumiFitDispVsMeanGPU,
21
- dropout_prob_kernel
22
- )
17
+ # ==========================================
18
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
19
+ # ==========================================
20
+ try:
21
+ # Case 1: Package
22
+ from .ControlDeviceGPU import ControlDevice
23
+ from .CoreGPU import (
24
+ hidden_calc_valsGPU,
25
+ NBumiFitModelGPU,
26
+ NBumiFitDispVsMeanGPU,
27
+ dropout_prob_kernel
28
+ )
29
+ except ImportError:
30
+ # Case 2: Local
31
+ try:
32
+ from ControlDeviceGPU import ControlDevice
33
+ from CoreGPU import (
34
+ hidden_calc_valsGPU,
35
+ NBumiFitModelGPU,
36
+ NBumiFitDispVsMeanGPU,
37
+ dropout_prob_kernel
38
+ )
39
+ except ImportError:
40
+ print("CRITICAL ERROR: Dependencies (ControlDeviceGPU, CoreGPU) not found.")
41
+ sys.exit(1)
23
42
 
24
43
  from cupy.sparse import csr_matrix as cp_csr_matrix
25
44
  import scipy.sparse as sp
@@ -385,9 +404,11 @@ def NBumiPlotDispVsMeanGPU(
385
404
  mean_expression = fit['vals']['tjs'].values / fit['vals']['nc']
386
405
  sizes = fit['sizes'].values
387
406
 
407
+ # 1. Get calibrated coefficients (using the fixed CoreGPU logic)
388
408
  coeffs = NBumiFitDispVsMeanGPU(fit, suppress_plot=True)
389
409
  intercept, slope = coeffs[0], coeffs[1]
390
410
 
411
+ # 2. Calculate the regression line
391
412
  log_mean_expr_range = np.linspace(
392
413
  np.log(mean_expression[mean_expression > 0].min()),
393
414
  np.log(mean_expression.max()),
@@ -396,8 +417,19 @@ def NBumiPlotDispVsMeanGPU(
396
417
  log_fitted_sizes = intercept + slope * log_mean_expr_range
397
418
  fitted_sizes = np.exp(log_fitted_sizes)
398
419
 
420
+ # 3. [FIX] Mask the 10k outliers for the SCATTER PLOT
421
+ # We create a visualization mask to hide the distracting "roof" at y=10000
422
+ mask_viz = (sizes < 9999.0) & (sizes > 0)
423
+
424
+ mean_expr_clean = mean_expression[mask_viz]
425
+ sizes_clean = sizes[mask_viz]
426
+
399
427
  plt.figure(figsize=(8, 6))
400
- plt.scatter(mean_expression, sizes, label='Observed Dispersion', alpha=0.5, s=8)
428
+
429
+ # Plot only the clean data
430
+ plt.scatter(mean_expr_clean, sizes_clean, label='Observed Dispersion', alpha=0.5, s=8)
431
+
432
+ # Plot the regression line (which was calculated correctly via CoreGPU)
401
433
  plt.plot(np.exp(log_mean_expr_range), fitted_sizes, color='red', label='Regression Fit', linewidth=2)
402
434
 
403
435
  plt.xscale('log')
@@ -16,8 +16,19 @@ except ImportError:
16
16
  print("CRITICAL ERROR: 'numba' not found. Please install it (pip install numba).")
17
17
  sys.exit(1)
18
18
 
19
- # Strict Relative Import
20
- from .ControlDeviceCPU import ControlDevice
19
+ # ==========================================
20
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
21
+ # ==========================================
22
+ try:
23
+ # Case 1: Running as an installed package
24
+ from .ControlDeviceCPU import ControlDevice
25
+ except ImportError:
26
+ # Case 2: Running locally
27
+ try:
28
+ from ControlDeviceCPU import ControlDevice
29
+ except ImportError:
30
+ print("CRITICAL ERROR: 'ControlDeviceCPU.py' not found.")
31
+ sys.exit(1)
21
32
 
22
33
  # ==========================================
23
34
  # NUMBA KERNELS (CPU)
@@ -18,10 +18,14 @@ except ImportError:
18
18
  cupy = None
19
19
  HAS_GPU = False
20
20
 
21
- # Package-compatible import
21
+ # ==========================================
22
+ # HYBRID IMPORT (PACKAGE VS LOCAL)
23
+ # ==========================================
22
24
  try:
25
+ # Case 1: Package
23
26
  from .ControlDeviceGPU import ControlDevice
24
27
  except ImportError:
28
+ # Case 2: Local
25
29
  try:
26
30
  from ControlDeviceGPU import ControlDevice
27
31
  except ImportError:
@@ -367,6 +371,3 @@ def NBumiPearsonResidualsCombinedGPU(
367
371
 
368
372
  if hasattr(adata_in, "file") and adata_in.file is not None: adata_in.file.close()
369
373
  print(f"Total time: {time.perf_counter() - start_time:.2f} seconds.\n")
370
-
371
-
372
-
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="M3Drop", # Name for pip (pip install M3Drop)
8
- version="0.4.58",
8
+ version="0.4.60",
9
9
  author="Tallulah Andrews",
10
10
  author_email="tandrew6@uwo.ca",
11
11
  description="A Python implementation of the M3Drop single-cell RNA-seq analysis tool.",
File without changes
File without changes
File without changes
File without changes
File without changes