forked from SynBioDex/Excel-to-SBOL
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from PeterHindes/SPUR-Rosalie
This update fixes the gui and makes it look better and work on mac, and work in a more extensible way.
- Loading branch information
Showing
26 changed files
with
386 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,4 +129,7 @@ dmypy.json | |
.pyre/ | ||
|
||
#vscode | ||
.vscode/ | ||
.vscode/ | ||
|
||
#Excel files | ||
*.xlsx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# This docker file coppies the src folder and installs the dependencies | ||
# we use python:3.12 as the base image | ||
# we also mount the output folder to the WORKDIR/output folder | ||
|
||
FROM python:3.12 | ||
|
||
# Set the working directory | ||
WORKDIR /app | ||
|
||
# Copy the current directory contents into the container at /app | ||
COPY ./src /app | ||
|
||
# Install any needed packages specified in requirements.txt | ||
RUN pip install --trusted-host pypi.python.org -r requirements.txt | ||
|
||
# Create a folder to store the output | ||
RUN mkdir output | ||
|
||
# Expose the port the app runs on | ||
EXPOSE 4269 | ||
|
||
# Run main.py when the container launches | ||
CMD ["python", "main.py"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
# Xperimental-data-convertor | ||
# Xperimental Data Connector | ||
|
||
This is a utility to link excel2sbol and excel to flapjack converter, interlink the resulting data, and upload it to synbiohub and flapjack. | ||
|
||
<img src="https://github.com/SynBioDex/Xperimental-Data-Connector/blob/Gonza10V-patch-1/images/xdc%20logo.png" alt="XDC logo" width="250"/> | ||
|
||
|
||
The Xperimental Data Connector (XDC) is a Python package that links excel2sbol and excel2flapjack, interlink the resulting data, and upload it to synbiohub and flapjack. | ||
|
||
This branch is only used to generate an Excel sheet for experiments. It does this by taking the cartesian product of the options selected to generate an experiment that covers all possible variations of the experiment. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
services: | ||
main: | ||
build: | ||
context: . | ||
dockerfile: Dockerfile | ||
volumes: | ||
- ./output:/app/output | ||
ports: | ||
- "4269:4269" |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/bash | ||
|
||
if [[ "$@" == *"--clean"* ]]; then | ||
rm -r venv | ||
fi | ||
|
||
if [ ! -d "venv" ]; then | ||
python3 -m venv venv | ||
. venv/bin/activate | ||
pip install -r src/requirements.txt | ||
else | ||
. venv/bin/activate | ||
fi | ||
|
||
if [[ "$@" == *"--debug"* ]]; then | ||
python3 src/main.py --debug | ||
else | ||
python src/main.py | ||
fi |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
from datetime import datetime | ||
import itertools | ||
from openpyxl import Workbook | ||
from openpyxl.styles import Font | ||
|
||
# Below is excel sheet generation code | ||
# it takes an argument that looks like [{'lb'}, {'top10', 'dh5a'}, {'repressilator'}, {'atc'}] | ||
def excel_sheet(lists, replicates): | ||
product = list(itertools.product(*lists)) | ||
wb = Workbook() | ||
sheet1 = wb.active | ||
sheet1.title = "Sample Design" | ||
titles = [ | ||
"Sample Design ID", | ||
"Media ID", | ||
"Strain ID", | ||
"Vector ID", | ||
"Supplement ID", | ||
] | ||
|
||
# Populate the first row with the titles | ||
for col_num, title in enumerate(titles, start=1): | ||
cell = sheet1.cell(row=1, column=col_num, value=title) | ||
cell.font = Font(bold=True) | ||
|
||
global sample_design_id | ||
sample_design_id = 1 | ||
|
||
# Populating the excel sheet with generated data | ||
for row_num, item in enumerate(product, start=2): | ||
# Sample ID counter for first row | ||
sheet1.cell(row=row_num, column=1, value=f"SampleDesign{sample_design_id}") | ||
sample_design_id += 1 | ||
|
||
# Continue to populate the rest of the row | ||
for col_num, value in enumerate(item, start=2): | ||
sheet1.cell(row=row_num, column=col_num, value=value) | ||
|
||
plate_96_wells_coordinates = [ | ||
(row, col) for row in range(1, 9) for col in range(1, 13) | ||
] | ||
|
||
def generate_sample_names(num_designs): | ||
global sample_names | ||
sample_names = [] | ||
for i in range(0, sample_design_id - 1): | ||
sample_names.append(f"SampleDesign{i+1}") | ||
return sample_names | ||
|
||
num_designs = 4 | ||
generate_sample_names(num_designs) | ||
|
||
# This creates/formats sheet 2 with the samples and their positions in a well plate | ||
sheet2 = wb.create_sheet(title="Sample") | ||
|
||
headers = ["Sample ID", "Row", "Column", "Well ID", "Sample Design ID"] | ||
|
||
for col_num, header in enumerate(headers, start=1): | ||
sheet2.cell(row=1, column=col_num, value=header) | ||
|
||
# Initializes the well count and sample ID count | ||
well_count = 0 | ||
sample_id = 1 | ||
|
||
for sd in sample_names: | ||
for r in range(replicates): | ||
row_data = [ | ||
f"Sample{sample_id}", | ||
plate_96_wells_coordinates[well_count][0], | ||
plate_96_wells_coordinates[well_count][1], | ||
f"Assay{+1}", | ||
sd, | ||
] | ||
sheet2.append(row_data) | ||
well_count += 1 | ||
sample_id += 1 | ||
|
||
# Auto fit column width code | ||
for col in sheet1.columns: | ||
max_length = 0 | ||
column = col[0].column_letter | ||
for cell in col: | ||
try: | ||
if len(str(cell.value)) > max_length: | ||
max_length = len(str(cell.value)) | ||
except: | ||
pass | ||
adjusted_width = max_length + 2 | ||
sheet1.column_dimensions[column].width = adjusted_width | ||
|
||
for col in sheet2.columns: | ||
max_length = 0 | ||
column = col[0].column_letter | ||
for cell in col: | ||
try: | ||
if len(str(cell.value)) > max_length: | ||
max_length = len(str(cell.value)) | ||
except: | ||
pass | ||
adjusted_width = max_length + 2 | ||
sheet2.column_dimensions[column].width = adjusted_width | ||
|
||
timestamp = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") | ||
filename = f"output/SampleDesign_{timestamp}.xlsx" | ||
|
||
wb.save(filename) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import argparse | ||
from flask import Flask, request, render_template, send_file | ||
import webbrowser | ||
from gensheet import excel_sheet | ||
|
||
app = Flask(__name__) | ||
|
||
@app.route("/") | ||
@app.route('/index.html') | ||
def index(): | ||
return send_file('templates/index.html', mimetype='text/html') | ||
|
||
@app.route('/options.json') | ||
def options(): | ||
return send_file('options.json', mimetype='application/json') | ||
|
||
@app.route('/favicon.ico') | ||
def favicon(): | ||
return send_file('favicon.ico', mimetype='image/vnd.microsoft.icon') | ||
|
||
# This takes an html form post and prints it to the console returning success to the browser | ||
@app.route('/submit', methods=['POST']) | ||
def submit(): | ||
# get the lists from the form | ||
lists = [ | ||
request.form.getlist('media'), | ||
request.form.getlist('strain'), | ||
request.form.getlist('supplement'), | ||
request.form.getlist('vector') | ||
] | ||
|
||
#check that all lists have at least one item | ||
for l in lists: | ||
if len(l) == 0: | ||
return render_template('response.html', | ||
message="Error, at least one item must be selected from each list, please go back.", | ||
color="red" | ||
) | ||
|
||
# get the number of replicates from the form | ||
replicates = int(request.form["replicates"]) | ||
|
||
# generate the excel sheet | ||
try: | ||
excel_sheet(lists, replicates) | ||
# Below is error handling code | ||
except Exception as e: | ||
return render_template('response.html', | ||
message=f"An error occurred during generation: {str(e)}\n\nPlease select a lower number of replicates and try again.", | ||
color="red" | ||
) | ||
|
||
# return success to the browser | ||
return render_template('response.html', | ||
message="Success", | ||
color="green" | ||
) | ||
|
||
port = 4269 | ||
# webbrowser.open('http://localhost:'+str(port)) | ||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser(description='Run Flask app with optional debug mode.') | ||
parser.add_argument('--debug', action='store_true', help='Run the Flask app in debug mode') | ||
args = parser.parse_args() | ||
|
||
app.run(debug=args.debug, port=port, host='0.0.0.0') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"media": ["lb", "m9"], | ||
"strain": ["top10", "dh5a"], | ||
"supplement": ["atc", "iptg"], | ||
"vector": ["repressilator", "toggleswitch"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
flask==3.0.3 | ||
openpyxl==3.1.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>LabGen UI</title> | ||
<style> | ||
#page { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
padding: 25px; | ||
margin: 25px; | ||
border-radius: 15px; | ||
background-color: rgb(0, 55, 77); | ||
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); | ||
} | ||
#submit { | ||
background-color: rgb(0, 126, 176); | ||
border-radius: 15px; | ||
border: none; | ||
cursor: pointer; | ||
} | ||
#form { | ||
width: 100%; | ||
} | ||
form { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 100%; | ||
} | ||
form > .section { | ||
background-color: rgb(0, 84, 118); | ||
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); | ||
padding: 10px; | ||
margin: 5px; | ||
border-radius: 10px; | ||
color: wheat; | ||
width: calc(100% - 20px); | ||
} | ||
|
||
body { | ||
background-color: rgb(4, 4, 41); | ||
color: wheat; | ||
margin: 0; | ||
} | ||
|
||
</style> | ||
</head> | ||
<body> | ||
<div id="page"> | ||
<h1>Welcome to LabGen</h1> | ||
<div id="form"></div> | ||
</div> | ||
|
||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script> | ||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> | ||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | ||
<script type="text/jsx""> | ||
|
||
// takes the section title and the options for the section | ||
function OptionsSection({title, name, options, selectFirst=false}) { | ||
return ( | ||
<div className="section"> | ||
<label>{title}</label> | ||
<div className="options"> | ||
{options.map((option, index) => ( | ||
<label key={name+index}><input type="checkbox" name={name} value={option} autoFocus={(index==0 && selectFirst)} />{option}</label> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
function OptionSlider({title, name, min, max, initialVal}) { | ||
const [value, setValue] = React.useState(initialVal); | ||
|
||
const handleChange = (event) => { | ||
setValue(event.target.value); | ||
}; | ||
|
||
return ( | ||
<div className="section"> | ||
<label>{title}</label> | ||
<div> | ||
<input type="range" name={name} min={min} max={max} defaultValue={value} onChange={handleChange} /> | ||
<span>{value}</span> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
function Form() { | ||
// Start with empty options list | ||
const [options, setOptions] = React.useState({ | ||
"media": [""], | ||
"strain": [""], | ||
"supplement": [""], | ||
"vector": [""] | ||
}); | ||
|
||
|
||
// Fetch the options JSON data when the component mounts | ||
React.useEffect(() => { | ||
fetch('/options.json') | ||
.then(response => response.json()) | ||
.then(data => {console.log(data);setOptions(data)}) | ||
.catch(error => console.error('Error fetching the JSON data:', error)); | ||
}, []); | ||
|
||
return ( | ||
<form action="/submit" method="post"> | ||
<OptionsSection title="Select Media" name="media" options={options.media} selectFirst={true} /> | ||
<OptionsSection title="Select a strain" name="strain" options={options.strain} /> | ||
<OptionsSection title="Select a supplement" name="supplement" options={options.supplement} /> | ||
<OptionsSection title="Select a vector" name="vector" options={options.vector} /> | ||
|
||
<OptionSlider title="Number of replicates" name="replicates" min="1" max="10" initialVal="1" /> | ||
|
||
<button className="section" type="submit" id="submit">Submit</button> | ||
</form> | ||
) | ||
} | ||
|
||
const form = document.getElementById('form'); | ||
const root = ReactDOM.createRoot(form); | ||
root.render(<Form />); | ||
</script> | ||
</body> | ||
</html> |
Oops, something went wrong.