bmtool 0.6.0__py3-none-any.whl → 0.6.1__py3-none-any.whl

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.
bmtool/SLURM.py CHANGED
@@ -3,6 +3,7 @@ import os
3
3
  import subprocess
4
4
  import json
5
5
  import requests
6
+ import shutil
6
7
 
7
8
 
8
9
  def check_job_status(job_id):
@@ -106,7 +107,6 @@ class seedSweep:
106
107
 
107
108
  print(f"JSON file '{self.json_file_path}' modified successfully with {self.param_name}={new_value}.", flush=True)
108
109
 
109
-
110
110
  def change_json_file_path(self,new_json_file_path):
111
111
  self.json_file_path = new_json_file_path
112
112
 
@@ -156,8 +156,7 @@ class multiSeedSweep(seedSweep):
156
156
 
157
157
 
158
158
  class SimulationBlock:
159
- def __init__(self, block_name, time, partition, nodes, ntasks, mem, simulation_cases, output_base_dir,account=None,additional_commands=None,
160
- status_list = ['COMPLETED', 'FAILED', 'CANCELLED']):
159
+ def __init__(self, block_name, time, partition, nodes, ntasks, mem, simulation_cases, output_base_dir,account=None,additional_commands=None,status_list = ['COMPLETED', 'FAILED', 'CANCELLED'],component_path=None):
161
160
  """
162
161
  Initializes the SimulationBlock instance.
163
162
 
@@ -187,6 +186,7 @@ class SimulationBlock:
187
186
  self.additional_commands = additional_commands if additional_commands is not None else []
188
187
  self.status_list = status_list
189
188
  self.job_ids = []
189
+ self.component_path = component_path
190
190
 
191
191
  def create_batch_script(self, case_name, command):
192
192
  """
@@ -207,6 +207,8 @@ class SimulationBlock:
207
207
  additional_commands_str = "\n".join(self.additional_commands)
208
208
  # Conditional account linegit
209
209
  account_line = f"#SBATCH --account={self.account}\n" if self.account else ""
210
+ env_var_component_path = f"export COMPONENT_PATH={self.component_path}" if self.component_path else ""
211
+
210
212
 
211
213
  # Write the batch script to the file
212
214
  with open(batch_script_path, 'w') as script_file:
@@ -224,6 +226,9 @@ class SimulationBlock:
224
226
  # Additional user-defined commands
225
227
  {additional_commands_str}
226
228
 
229
+ #enviroment vars
230
+ {env_var_component_path}
231
+
227
232
  export OUTPUT_DIR={case_output_dir}
228
233
 
229
234
  {command}
@@ -272,7 +277,6 @@ export OUTPUT_DIR={case_output_dir}
272
277
  return False
273
278
  return True
274
279
 
275
-
276
280
  def check_block_running(self):
277
281
  """checks if a job is running
278
282
 
@@ -284,9 +288,21 @@ export OUTPUT_DIR={case_output_dir}
284
288
  if status != 'RUNNING': #
285
289
  return False
286
290
  return True
291
+
292
+ def check_block_submited(self):
293
+ """checks if a job is running
287
294
 
295
+ Returns:
296
+ bool: True if jobs are RUNNING false if anything else
297
+ """
298
+ for job_id in self.job_ids:
299
+ status = check_job_status(job_id)
300
+ if status != 'PENDING': #
301
+ return False
302
+ return True
288
303
 
289
- class SequentialBlockRunner:
304
+
305
+ class BlockRunner:
290
306
  """
291
307
  Class to handle submitting multiple blocks sequentially.
292
308
 
@@ -297,17 +313,23 @@ class SequentialBlockRunner:
297
313
  webhook (str): a microsoft webhook for teams. When used will send teams messages to the hook!
298
314
  """
299
315
 
300
- def __init__(self, blocks, json_editor=None, param_values=None, check_interval=200,webhook=None):
316
+ def __init__(self, blocks, json_editor=None,json_file_path=None, param_name=None,
317
+ param_values=None, check_interval=60,syn_dict_list = None,
318
+ webhook=None):
301
319
  self.blocks = blocks
302
320
  self.json_editor = json_editor
303
321
  self.param_values = param_values
304
322
  self.check_interval = check_interval
305
323
  self.webhook = webhook
324
+ self.param_name = param_name
325
+ self.json_file_path = json_file_path
326
+ self.syn_dict_list = syn_dict_list
306
327
 
307
328
  def submit_blocks_sequentially(self):
308
329
  """
309
- Submits all blocks sequentially, ensuring each block starts only after the previous block has completed.
330
+ Submits all blocks sequentially, ensuring each block starts only after the previous block has completed or is running.
310
331
  Updates the JSON file with new parameters before each block run.
332
+ json file path should be the path WITH the components folder
311
333
  """
312
334
  for i, block in enumerate(self.blocks):
313
335
  # Update JSON file with new parameter value
@@ -317,15 +339,14 @@ class SequentialBlockRunner:
317
339
  if len(self.blocks) != len(self.param_values):
318
340
  raise Exception("Number of blocks needs to each number of params given")
319
341
  new_value = self.param_values[i]
320
- # NGL didnt test the multi but should work
321
- if isinstance(self.json_editor, multiSeedSweep):
322
- self.json_editor.edit_all_jsons(new_value)
323
- elif isinstance(self.json_editor,seedSweep):
324
- print(f"Updating JSON file with parameter value for block: {block.block_name}", flush=True)
325
- self.json_editor.edit_json(new_value)
326
- else:
327
- raise Exception("json editor provided but not a seedSweep class not sure what your doing?!?")
328
342
 
343
+ if self.syn_dict_list == None:
344
+ json_editor = seedSweep(self.json_file_path, self.param_name)
345
+ json_editor.edit_json(new_value)
346
+ else:
347
+ json_editor = multiSeedSweep(self.json_file_path,self.param_name,
348
+ self.syn_dict_list,base_ratio=1)
349
+ json_editor.edit_all_jsons(new_value)
329
350
 
330
351
  # Submit the block
331
352
  print(f"Submitting block: {block.block_name}", flush=True)
@@ -335,7 +356,7 @@ class SequentialBlockRunner:
335
356
  send_teams_message(self.webhook,message)
336
357
 
337
358
  # Wait for the block to complete
338
- if i == len(self.blocks) - 1: # Corrected index to check the last block
359
+ if i == len(self.blocks) - 1:
339
360
  while not block.check_block_completed():
340
361
  print(f"Waiting for the last block {i} to complete...")
341
362
  time.sleep(self.check_interval)
@@ -350,3 +371,43 @@ class SequentialBlockRunner:
350
371
  message = "SIMULATION UPDATE: Simulation are Done!"
351
372
  send_teams_message(self.webhook,message)
352
373
 
374
+ def submit_blocks_parallel(self):
375
+ """
376
+ submits all the blocks at once onto the queue. To do this the components dir will be cloned and each block will have its own.
377
+ Also the json_file_path should be the path after the components dir
378
+ """
379
+ if self.webhook:
380
+ message = "SIMULATION UPDATE: Simulations have been submited in parallel!"
381
+ send_teams_message(self.webhook,message)
382
+ for i, block in enumerate(self.blocks):
383
+ if block.component_path == None:
384
+ raise Exception("Unable to use parallel submitter without defining the component path")
385
+ new_value = self.param_values[i]
386
+
387
+ source_dir = block.component_path
388
+ destination_dir = f"{source_dir}{i+1}"
389
+ block.component_path = destination_dir
390
+
391
+ shutil.copytree(source_dir, destination_dir) # create new components folder
392
+ json_file_path = os.path.join(destination_dir,self.json_file_path)
393
+ if self.syn_dict_list == None:
394
+ json_editor = seedSweep(json_file_path, self.param_name)
395
+ json_editor.edit_json(new_value)
396
+ else:
397
+ json_editor = multiSeedSweep(json_file_path,self.param_name,
398
+ self.syn_dict_list,base_ratio=1)
399
+ json_editor.edit_all_jsons(new_value)
400
+
401
+ # submit block with new component path
402
+ print(f"Submitting block: {block.block_name}", flush=True)
403
+ block.submit_block()
404
+ if i == len(self.blocks) - 1:
405
+ while not block.check_block_completed():
406
+ print(f"Waiting for the last block {i} to complete...")
407
+ time.sleep(self.check_interval)
408
+
409
+ if self.webhook:
410
+ message = "SIMULATION UPDATE: Simulations are Done!"
411
+ send_teams_message(self.webhook,message)
412
+
413
+
bmtool/bmplot.py CHANGED
@@ -306,46 +306,47 @@ def gap_junction_matrix(config=None,title=None,sources=None, targets=None, sids=
306
306
 
307
307
 
308
308
  def filter_rows(syn_info, data, source_labels, target_labels):
309
- new_syn_info = syn_info
310
- new_data = data
311
- new_source_labels = source_labels
312
- new_target_labels = target_labels
313
- for row in new_data:
314
- row_index = -1
315
- try:
316
- if((np.isnan(row).all())): #checks if all of a row is nan
317
- row_index = np.where(np.isnan(new_data)==np.isnan(row))[0][0]
318
- except:
319
- row_index = -1
320
- finally:
321
- if(all(x==0 for x in row)): #checks if all of a row is zeroes
322
- row_index = np.where(new_data==row)[0][0]
323
- if row_index!=-1: #deletes corresponding row accordingly in all relevant variables.
324
- new_syn_info = np.delete(new_syn_info,row_index,0)
325
- new_data = np.delete(new_data,row_index,0)
326
- new_source_labels = np.delete(new_source_labels,row_index)
327
- return new_syn_info, new_data,new_source_labels,new_target_labels
328
-
329
- def filter_rows_and_columns(syn_info,data,source_labels,target_labels):
309
+ # Identify rows with all NaN or all zeros
310
+ valid_rows = ~np.all(np.isnan(data), axis=1) & ~np.all(data == 0, axis=1)
311
+
312
+ # Filter rows based on valid_rows mask
313
+ new_syn_info = syn_info[valid_rows]
314
+ new_data = data[valid_rows]
315
+ new_source_labels = np.array(source_labels)[valid_rows]
316
+
317
+ return new_syn_info, new_data, new_source_labels, target_labels
318
+
319
+ def filter_rows_and_columns(syn_info, data, source_labels, target_labels):
320
+ # Filter rows first
330
321
  syn_info, data, source_labels, target_labels = filter_rows(syn_info, data, source_labels, target_labels)
331
- transposed_syn_info = np.transpose(syn_info) #transpose everything and put it in to make sure columns get filtered
322
+
323
+ # Transpose data to filter columns
324
+ transposed_syn_info = np.transpose(syn_info)
332
325
  transposed_data = np.transpose(data)
333
326
  transposed_source_labels = target_labels
334
327
  transposed_target_labels = source_labels
335
- syn_info, data, source_labels, target_labels = filter_rows(transposed_syn_info, transposed_data, transposed_source_labels, transposed_target_labels)
336
- filtered_syn_info = np.transpose(syn_info) #transpose everything back to original order after filtering.
337
- filtered_data = np.transpose(data)
338
- filtered_source_labels = target_labels
339
- filtered_target_labels = source_labels
340
- return filtered_syn_info,filtered_data,filtered_source_labels,filtered_target_labels
328
+
329
+ # Filter columns (by treating them as rows in transposed data)
330
+ transposed_syn_info, transposed_data, transposed_source_labels, transposed_target_labels = filter_rows(
331
+ transposed_syn_info, transposed_data, transposed_source_labels, transposed_target_labels
332
+ )
333
+
334
+ # Transpose back to original orientation
335
+ filtered_syn_info = np.transpose(transposed_syn_info)
336
+ filtered_data = np.transpose(transposed_data)
337
+ filtered_source_labels = transposed_target_labels # Back to original source_labels
338
+ filtered_target_labels = transposed_source_labels # Back to original target_labels
339
+
340
+ return filtered_syn_info, filtered_data, filtered_source_labels, filtered_target_labels
341
+
341
342
 
342
343
  syn_info, data, source_labels, target_labels = filter_rows_and_columns(syn_info, data, source_labels, target_labels)
343
344
 
344
345
  if title == None or title=="":
345
346
  title = 'Gap Junction'
346
- if type == 'convergence':
347
+ if method == 'convergence':
347
348
  title+=' Syn Convergence'
348
- elif type == 'percent':
349
+ elif method == 'percent':
349
350
  title+=' Percent Connectivity'
350
351
  plot_connection_info(syn_info,data,source_labels,target_labels,title, save_file=save_file)
351
352
  return
@@ -535,35 +536,42 @@ def edge_histogram_matrix(config=None,sources = None,targets=None,sids=None,tids
535
536
  fig.text(0.04, 0.5, 'Source', va='center', rotation='vertical')
536
537
  plt.draw()
537
538
 
538
- def plot_connection_info(text, num, source_labels,target_labels, title, syn_info='0', save_file=None,return_dict=None):
539
+ def plot_connection_info(text, num, source_labels, target_labels, title, syn_info='0', save_file=None, return_dict=None):
539
540
  """
540
- write about function here
541
+ Function to plot connection information as a heatmap, including handling missing source and target values.
542
+ If there is no source or target, set the value to 0.
541
543
  """
542
544
 
543
- #num = pd.DataFrame(num).fillna('nc').to_numpy() # replace nan with nc * does not work with imshow
545
+ # Ensure text dimensions match num dimensions
546
+ num_source = len(source_labels)
547
+ num_target = len(target_labels)
544
548
 
545
- num_source=len(source_labels)
546
- num_target=len(target_labels)
549
+ # Set color map
547
550
  matplotlib.rc('image', cmap='viridis')
548
551
 
549
- fig1, ax1 = plt.subplots(figsize=(num_source,num_target))
552
+ # Create figure and axis for the plot
553
+ fig1, ax1 = plt.subplots(figsize=(num_source, num_target))
554
+ num = np.nan_to_num(num, nan=0) # replace NaN with 0
550
555
  im1 = ax1.imshow(num)
551
- #fig.colorbar(im, ax=ax,shrink=0.4)
552
- # We want to show all ticks...
556
+
557
+ # Set ticks and labels for source and target
553
558
  ax1.set_xticks(list(np.arange(len(target_labels))))
554
559
  ax1.set_yticks(list(np.arange(len(source_labels))))
555
- # ... and label them with the respective list entries
556
560
  ax1.set_xticklabels(target_labels)
557
- ax1.set_yticklabels(source_labels,size=12, weight = 'semibold')
558
- # Rotate the tick labels and set their alignment.
561
+ ax1.set_yticklabels(source_labels, size=12, weight='semibold')
562
+
563
+ # Rotate the tick labels for better visibility
559
564
  plt.setp(ax1.get_xticklabels(), rotation=45, ha="right",
560
- rotation_mode="anchor", size=12, weight = 'semibold')
565
+ rotation_mode="anchor", size=12, weight='semibold')
561
566
 
567
+ # Dictionary to store connection information
562
568
  graph_dict = {}
563
- # Loop over data dimensions and create text annotations.
569
+
570
+ # Loop over data dimensions and create text annotations
564
571
  for i in range(num_source):
565
572
  for j in range(num_target):
566
- edge_info = text[i, j]
573
+ # Get the edge info, or set it to '0' if it's missing
574
+ edge_info = text[i, j] if text[i, j] is not None else 0
567
575
 
568
576
  # Initialize the dictionary for the source node if not already done
569
577
  if source_labels[i] not in graph_dict:
@@ -571,35 +579,41 @@ def plot_connection_info(text, num, source_labels,target_labels, title, syn_info
571
579
 
572
580
  # Add edge info for the target node
573
581
  graph_dict[source_labels[i]][target_labels[j]] = edge_info
574
- if syn_info =='2' or syn_info =='3':
575
- if num_source > 8 and num_source <20:
582
+
583
+ # Set text annotations based on syn_info type
584
+ if syn_info == '2' or syn_info == '3':
585
+ if num_source > 8 and num_source < 20:
576
586
  fig_text = ax1.text(j, i, edge_info,
577
- ha="center", va="center", color="w",rotation=37.5, size=8, weight = 'semi\bold')
587
+ ha="center", va="center", color="w", rotation=37.5, size=8, weight='semibold')
578
588
  elif num_source > 20:
579
589
  fig_text = ax1.text(j, i, edge_info,
580
- ha="center", va="center", color="w",rotation=37.5, size=7, weight = 'semibold')
590
+ ha="center", va="center", color="w", rotation=37.5, size=7, weight='semibold')
581
591
  else:
582
592
  fig_text = ax1.text(j, i, edge_info,
583
- ha="center", va="center", color="w",rotation=37.5, size=11, weight = 'semibold')
593
+ ha="center", va="center", color="w", rotation=37.5, size=11, weight='semibold')
584
594
  else:
585
595
  fig_text = ax1.text(j, i, edge_info,
586
- ha="center", va="center", color="w", size=11, weight = 'semibold')
587
-
588
- ax1.set_ylabel('Source', size=11, weight = 'semibold')
589
- ax1.set_xlabel('Target', size=11, weight = 'semibold')
590
- ax1.set_title(title,size=20, weight = 'semibold')
591
- #plt.tight_layout()
592
- notebook = is_notebook()
596
+ ha="center", va="center", color="w", size=11, weight='semibold')
597
+
598
+ # Set labels and title for the plot
599
+ ax1.set_ylabel('Source', size=11, weight='semibold')
600
+ ax1.set_xlabel('Target', size=11, weight='semibold')
601
+ ax1.set_title(title, size=20, weight='semibold')
602
+
603
+ # Display the plot or save it based on the environment and arguments
604
+ notebook = is_notebook() # Check if running in a Jupyter notebook
593
605
  if notebook == False:
594
606
  fig1.show()
607
+
595
608
  if save_file:
596
609
  plt.savefig(save_file)
610
+
597
611
  if return_dict:
598
612
  return graph_dict
599
613
  else:
600
614
  return
601
615
 
602
- def connector_percent_matrix(csv_path: str = None, exclude_strings=None, title: str = 'Percent connection matrix', pop_order=None) -> None:
616
+ def connector_percent_matrix(csv_path: str = None, exclude_strings=None, assemb_key=None, title: str = 'Percent connection matrix', pop_order=None) -> None:
603
617
  """
604
618
  Generates and plots a connection matrix based on connection probabilities from a CSV file produced by bmtool.connector.
605
619
 
@@ -633,6 +647,7 @@ def connector_percent_matrix(csv_path: str = None, exclude_strings=None, title:
633
647
  # Filter the DataFrame based on exclude_strings
634
648
  def filter_dataframe(df, column_name, exclude_strings):
635
649
  def process_string(string):
650
+
636
651
  match = re.search(r"\[\'(.*?)\'\]", string)
637
652
  if exclude_strings and any(ex_string in string for ex_string in exclude_strings):
638
653
  return None
@@ -640,17 +655,55 @@ def connector_percent_matrix(csv_path: str = None, exclude_strings=None, title:
640
655
  filtered_string = match.group(1)
641
656
  if 'Gap' in string:
642
657
  filtered_string = filtered_string + "-Gap"
658
+ if assemb_key in string:
659
+ filtered_string = filtered_string + assemb_key
643
660
  return filtered_string # Return matched string
644
661
 
645
662
  return string # If no match, return the original string
646
-
663
+
647
664
  df[column_name] = df[column_name].apply(process_string)
648
665
  df = df.dropna(subset=[column_name])
666
+
649
667
  return df
650
668
 
651
669
  df = filter_dataframe(df, 'Source', exclude_strings)
652
670
  df = filter_dataframe(df, 'Target', exclude_strings)
671
+
672
+ #process assem rows and combine them into one prob per assem type
673
+ assems = df[df['Source'].str.contains(assemb_key)]
674
+ unique_sources = assems['Source'].unique()
653
675
 
676
+ for source in unique_sources:
677
+ source_assems = assems[assems['Source'] == source]
678
+ unique_targets = source_assems['Target'].unique() # Filter targets for the current source
679
+
680
+ for target in unique_targets:
681
+ # Filter the assemblies with the current source and target
682
+ unique_assems = source_assems[source_assems['Target'] == target]
683
+
684
+ # find the prob of a conn
685
+ forward_probs = []
686
+ for _,row in unique_assems.iterrows():
687
+ selected_percentage = row[selected_column]
688
+ selected_percentage = [float(p) for p in selected_percentage.strip('[]').split()]
689
+ if len(selected_percentage) == 1 or len(selected_percentage) == 2:
690
+ forward_probs.append(selected_percentage[0])
691
+ if len(selected_percentage) == 3:
692
+ forward_probs.append(selected_percentage[0])
693
+ forward_probs.append(selected_percentage[1])
694
+
695
+ mean_probs = np.mean(forward_probs)
696
+ source = source.replace(assemb_key, "")
697
+ target = target.replace(assemb_key, "")
698
+ new_row = pd.DataFrame({
699
+ 'Source': [source],
700
+ 'Target': [target],
701
+ 'Percent connectionivity within possible connections': [mean_probs],
702
+ 'Percent connectionivity within all connections': [0]
703
+ })
704
+
705
+ df = pd.concat([df, new_row], ignore_index=False)
706
+
654
707
  # Prepare connection data
655
708
  connection_data = {}
656
709
  for _, row in df.iterrows():
@@ -671,14 +724,18 @@ def connector_percent_matrix(csv_path: str = None, exclude_strings=None, title:
671
724
  if source in populations and target in populations:
672
725
  source_idx = populations.index(source)
673
726
  target_idx = populations.index(target)
674
- connection_matrix[source_idx][target_idx] = probabilities[0]
675
- if len(probabilities) == 1:
727
+
728
+ if type(probabilities) == float:
729
+ connection_matrix[source_idx][target_idx] = probabilities
730
+ elif len(probabilities) == 1:
676
731
  connection_matrix[source_idx][target_idx] = probabilities[0]
677
- if len(probabilities) == 2:
732
+ elif len(probabilities) == 2:
678
733
  connection_matrix[source_idx][target_idx] = probabilities[0]
679
- if len(probabilities) == 3:
734
+ elif len(probabilities) == 3:
680
735
  connection_matrix[source_idx][target_idx] = probabilities[0]
681
736
  connection_matrix[target_idx][source_idx] = probabilities[1]
737
+ else:
738
+ raise Exception("unsupported format")
682
739
 
683
740
  # Plotting
684
741
  fig, ax = plt.subplots(figsize=(10, 8))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bmtool
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: BMTool
5
5
  Home-page: https://github.com/cyneuro/bmtool
6
6
  Download-URL:
@@ -1,7 +1,7 @@
1
- bmtool/SLURM.py,sha256=AX5MKV7dD-XwS8SROnW1IyesZ3jDwMf0txO6mHxTbuw,13694
1
+ bmtool/SLURM.py,sha256=woANwh6ToP5nsEkVX1qlINvkfGMp3P7Roq4O8X611M8,16371
2
2
  bmtool/__init__.py,sha256=ZStTNkAJHJxG7Pwiy5UgCzC4KlhMS5pUNPtUJZVwL_Y,136
3
3
  bmtool/__main__.py,sha256=TmFkmDxjZ6250nYD4cgGhn-tbJeEm0u-EMz2ajAN9vE,650
4
- bmtool/bmplot.py,sha256=vIknbCwBSJNsr4m53SymteKdvyE0Omn3Xv8udzroeKc,51825
4
+ bmtool/bmplot.py,sha256=HuzBRrVsD6xFM-siWdT_t6XqffFhK6LFcGF7RttR6pQ,54013
5
5
  bmtool/connectors.py,sha256=2vVUsqYMaCuWZ-4C5eUzqwsFItFM9vm0ytZdRQdWgoc,72243
6
6
  bmtool/graphs.py,sha256=K8BiughRUeXFVvAgo8UzrwpSClIVg7UfmIcvtEsEsk0,6020
7
7
  bmtool/manage.py,sha256=_lCU0qBQZ4jSxjzAJUd09JEetb--cud7KZgxQFbLGSY,657
@@ -16,9 +16,9 @@ bmtool/util/commands.py,sha256=zJF-fiLk0b8LyzHDfvewUyS7iumOxVnj33IkJDzux4M,64396
16
16
  bmtool/util/util.py,sha256=00vOAwTVIifCqouBoFoT0lBashl4fCalrk8fhg_Uq4c,56654
17
17
  bmtool/util/neuron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  bmtool/util/neuron/celltuner.py,sha256=xSRpRN6DhPFz4q5buq_W8UmsD7BbUrkzYBEbKVloYss,87194
19
- bmtool-0.6.0.dist-info/LICENSE,sha256=qrXg2jj6kz5d0EnN11hllcQt2fcWVNumx0xNbV05nyM,1068
20
- bmtool-0.6.0.dist-info/METADATA,sha256=8qbBKGJWEedc2N_xdm9OYb66bCQScXxb2faMQvdJvsQ,19113
21
- bmtool-0.6.0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
22
- bmtool-0.6.0.dist-info/entry_points.txt,sha256=0-BHZ6nUnh0twWw9SXNTiRmKjDnb1VO2DfG_-oprhAc,45
23
- bmtool-0.6.0.dist-info/top_level.txt,sha256=gpd2Sj-L9tWbuJEd5E8C8S8XkNm5yUE76klUYcM-eWM,7
24
- bmtool-0.6.0.dist-info/RECORD,,
19
+ bmtool-0.6.1.dist-info/LICENSE,sha256=qrXg2jj6kz5d0EnN11hllcQt2fcWVNumx0xNbV05nyM,1068
20
+ bmtool-0.6.1.dist-info/METADATA,sha256=Ir2-vUb_0qfRxIJlj5iDuKVbnp5gFe6SOsqgCBWRdU4,19113
21
+ bmtool-0.6.1.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
22
+ bmtool-0.6.1.dist-info/entry_points.txt,sha256=0-BHZ6nUnh0twWw9SXNTiRmKjDnb1VO2DfG_-oprhAc,45
23
+ bmtool-0.6.1.dist-info/top_level.txt,sha256=gpd2Sj-L9tWbuJEd5E8C8S8XkNm5yUE76klUYcM-eWM,7
24
+ bmtool-0.6.1.dist-info/RECORD,,
File without changes