Skip to content

Commit

Permalink
Merge branch 'main' into retry-ci
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyCBakerPhD authored Feb 20, 2024
2 parents 49cc93b + d80764a commit 1f5ad96
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 37 deletions.
13 changes: 12 additions & 1 deletion pyflask/apis/neuroconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
get_all_interface_info,
get_all_converter_info,
locate_data,
autocomplete_format_string,
get_source_schema,
get_metadata_schema,
convert_to_nwb,
validate_metadata,
listen_to_neuroconv_events,
generate_dataset,
inspect_nwb_file,
inspect_nwb_folder,
inspect_multiple_filesystem_objects,
Expand Down Expand Up @@ -78,6 +78,17 @@ def post(self):
neuroconv_api.abort(500, str(exception))


@neuroconv_api.route("/locate/autocomplete")
class Locate(Resource):
@neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"})
def post(self):
try:
return autocomplete_format_string(neuroconv_api.payload)
except Exception as exception:
if notBadRequestException(exception):
neuroconv_api.abort(500, str(exception))


@neuroconv_api.route("/metadata")
class Metadata(Resource):
@neuroconv_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"})
Expand Down
1 change: 1 addition & 0 deletions pyflask/manageNeuroconv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
get_all_interface_info,
get_all_converter_info,
locate_data,
autocomplete_format_string,
get_source_schema,
get_metadata_schema,
convert_to_nwb,
Expand Down
68 changes: 63 additions & 5 deletions pyflask/manageNeuroconv/manage_neuroconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
announcer = MessageAnnouncer()


def is_path_contained(child, parent):
parent = Path(parent)
child = Path(child)

# Attempt to construct a relative path from parent to child
try:
child.relative_to(parent)
return True
except ValueError:
return False


def replace_nan_with_none(data):
if isinstance(data, dict):
# If it's a dictionary, iterate over its items and replace NaN values with None
Expand Down Expand Up @@ -109,6 +121,39 @@ def coerce_schema_compliance_recursive(obj, schema):
)


def autocomplete_format_string(info: dict) -> str:
from neuroconv.tools.path_expansion import construct_path_template

base_directory = info["base_directory"]
filesystem_entry_path = info["path"]

if not is_path_contained(filesystem_entry_path, base_directory):
raise ValueError("Path is not contained in the provided base directory.")

full_format_string = construct_path_template(
filesystem_entry_path,
subject_id=info["subject_id"],
session_id=info["session_id"],
**info["additional_metadata"],
)

parent = Path(base_directory).resolve()
child = Path(full_format_string).resolve()

format_string = str(child.relative_to(parent))

to_locate_info = dict(base_directory=base_directory)

if Path(filesystem_entry_path).is_dir():
to_locate_info["folder_path"] = format_string
else:
to_locate_info["file_path"] = format_string

all_matched = locate_data(dict(autocomplete=to_locate_info))

return dict(matched=all_matched, format_string=format_string)


def locate_data(info: dict) -> dict:
"""Locate data from the specifies directories using fstrings."""
from neuroconv.tools import LocalPathExpander
Expand Down Expand Up @@ -163,19 +208,32 @@ def get_class_ref_in_docstring(input_string):


def derive_interface_info(interface):

info = {"keywords": getattr(interface, "keywords", []), "description": ""}
if interface.__doc__:

if hasattr(interface, "associated_suffixes"):
info["suffixes"] = interface.associated_suffixes

if hasattr(interface, "info"):
info["description"] = interface.info

elif interface.__doc__:
info["description"] = re.sub(
remove_extra_spaces_pattern, " ", re.sub(doc_pattern, r"<code>\1</code>", interface.__doc__)
)

info["name"] = interface.__name__

return info


def get_all_converter_info() -> dict:
from neuroconv import converters
from neuroconv.converters import converter_list

return {name: derive_interface_info(converter) for name, converter in module_to_dict(converters).items()}
return {
getattr(converter, "display_name", converter.__name__) or converter.__name__: derive_interface_info(converter)
for converter in converter_list
}


def get_all_interface_info() -> dict:
Expand All @@ -201,7 +259,7 @@ def get_all_interface_info() -> dict:
]

return {
interface.__name__: derive_interface_info(interface)
getattr(interface, "display_name", interface.__name__) or interface.__name__: derive_interface_info(interface)
for interface in interface_list
if not interface.__name__ in exclude_interfaces_from_selection
}
Expand Down Expand Up @@ -609,7 +667,7 @@ def generate_dataset(input_path: str, output_path: str):
if base_id in file:
os.rename(os.path.join(root, file), os.path.join(root, file.replace(base_id, full_id)))

phy_output_dir.symlink_to(phy_base_directory, True)
copytree(phy_base_directory, phy_output_dir)

return {"output_path": str(output_path)}

Expand Down
4 changes: 3 additions & 1 deletion pyflask/tests/test_neuroconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ def test_get_all_interfaces(client):
"^.*Interface$": {
"type": "object",
"properties": {
"name": {"type": "string"},
"suffixes": {"type": "array", "items": {"type": "string"}},
"label": {"type": "string"},
"description": {"type": "string"},
"keywords": {"type": "array", "items": {"type": "string"}},
},
"additionalProperties": False,
"required": ["keywords"],
"required": ["name", "keywords"],
}
},
},
Expand Down
10 changes: 0 additions & 10 deletions src/renderer/src/stories/FileSystemSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,6 @@ export class FilesystemSelector extends LitElement {

const len = isArray ? this.value.length : 0;

if (isArray) {
resolved = this.value.map((str) => str.replaceAll("\\", "/"));
isUpdated = JSON.stringify(resolved) !== JSON.stringify(this.value);
} else {
resolved = typeof this.value === "string" ? this.value.replaceAll("\\", "/") : this.value;
isUpdated = resolved !== this.value;
}

if (isUpdated) this.#handleFiles(resolved); // Notify of the change to the separators

const resolvedValueDisplay = isArray
? len > 1
? `${this.value[0]} and ${len - 1} other${len > 2 ? "s" : ""}`
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/stories/JSONSchemaForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,8 @@ export class JSONSchemaForm extends LitElement {
results: { ...nestedResults },
globals: this.globals?.[name],

controls: this.controls[name],

onUpdate: (internalPath, value, forceUpdate) => {
const path = [...localPath, ...internalPath];
this.updateData(path, value, forceUpdate);
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/stories/JSONSchemaInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export const isEditableObject = (schema, value) =>
export const isAdditionalProperties = (pattern) => pattern === "additional";
export const isPatternProperties = (pattern) => pattern && !isAdditionalProperties(pattern);

export const getEditableItems = (value, pattern, { name, schema } = {}) => {
export const getEditableItems = (value = {}, pattern, { name, schema } = {}) => {
let items = Object.entries(value);

const allowAdditionalProperties = isAdditionalProperties(pattern);
Expand Down
54 changes: 43 additions & 11 deletions src/renderer/src/stories/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ export class Search extends LitElement {
z-index: 1;
}
.structured-keywords {
font-size: 90%;
color: dimgrey;
}
.option:hover {
background: #f2f2f2;
cursor: pointer;
Expand Down Expand Up @@ -204,8 +209,12 @@ export class Search extends LitElement {
const options = this.shadowRoot.querySelectorAll(".option");
this.#options = Array.from(options).map((option) => {
const keywordString = option.getAttribute("data-keywords");
const keywords = keywordString ? JSON.parse(keywordString) : [];
return { option, keywords, label: option.querySelector(".label").innerText };
const structuredKeywordString = option.getAttribute("data-structured-keywords");

const keywords = keywordString ? JSON.parse(option.getAttribute("data-keywords")) : [];
const structuredKeywords = structuredKeywordString ? JSON.parse(structuredKeywordString) : {};

return { option, keywords, structuredKeywords, label: option.querySelector(".label").innerText };
});

this.#initialize();
Expand Down Expand Up @@ -256,8 +265,8 @@ export class Search extends LitElement {
});

// Check if the input value matches any of the keywords
this.#options.forEach(({ option, keywords = [] }, i) => {
keywords.forEach((keyword) => {
this.#options.forEach(({ option, keywords = [], structuredKeywords = {} }, i) => {
[...keywords, ...Object.values(structuredKeywords).flat()].forEach((keyword) => {
if (keyword.toLowerCase().includes(input.toLowerCase()) && !toShow.includes(i)) toShow.push(i);
});
});
Expand Down Expand Up @@ -317,13 +326,22 @@ export class Search extends LitElement {
listItemElement.classList.add("option");
listItemElement.setAttribute("hidden", "");
listItemElement.setAttribute("tabindex", -1);
if (option.keywords) listItemElement.setAttribute("data-keywords", JSON.stringify(option.keywords));

const { disabled, structuredKeywords, keywords } = option;

if (structuredKeywords)
listItemElement.setAttribute(
"data-structured-keywords",
JSON.stringify(option.structuredKeywords)
);
if (keywords) listItemElement.setAttribute("data-keywords", JSON.stringify(option.keywords));

listItemElement.addEventListener("click", (clickEvent) => {
clickEvent.stopPropagation();
this.#onSelect(option);
});

if (option.disabled) listItemElement.setAttribute("disabled", "");
if (disabled) listItemElement.setAttribute("disabled", "");

const container = document.createElement("div");

Expand All @@ -346,11 +364,25 @@ export class Search extends LitElement {

container.appendChild(label);

if (option.keywords) {
const keywords = document.createElement("small");
keywords.classList.add("keywords");
keywords.innerText = option.keywords.join(", ");
container.appendChild(keywords);
if (keywords) {
const keywordsElement = document.createElement("small");
keywordsElement.classList.add("keywords");
keywordsElement.innerText = option.keywords.join(", ");
container.appendChild(keywordsElement);
}

if (structuredKeywords) {
const div = document.createElement("div");
div.classList.add("structured-keywords");

Object.entries(structuredKeywords).forEach(([key, value]) => {
const keywordsElement = document.createElement("small");
const capitalizedKey = key[0].toUpperCase() + key.slice(1);
keywordsElement.innerHTML = `<b>${capitalizedKey}:</b> ${value.join(", ")}`;
div.appendChild(keywordsElement);
});

container.appendChild(div);
}

listItemElement.append(container);
Expand Down
Loading

0 comments on commit 1f5ad96

Please sign in to comment.