diff --git a/README.md b/README.md index e6259076..7f57d673 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,17 @@ The user will be able to know when that process has concluded by revisiting the At this point, the manuscript should be searchable within the main website. +### Indexing MEI and activating OMR search + +To add OMR search functionality to a manuscript, a set of MEI 5 files for the manuscript needs to be added to the [production MEI files repository](https://github.com/DDMAL/production_mei_files). Add these files in a folder named with the Cantus Database source ID for the manuscript. The mei files must be named by the following convention: *_[folio_number].mei. Once these are committed to the main branch of the production MEI files repo, they can be pulled with the rest of the Cantus Ultimus code base (the production MEI files repository is a submodule of the CU repo). + +In the running `app` container, run the `index_manuscript_mei` command with the first command line argument being the source ID of the manuscript. Additional command line options can be found in that command's help text. Once this command has completed, the manuscript MEI has been indexed in Solr. + +Go to the Cantus Ultimus admin page and ensure that the "Neume Search" and "Pitch Search" plugins exist in the database. If they do not, simply create new plugins with those names. Then, find the manuscript whose MEI you are adding in the admin panel. On that manuscript's admin page, select either the "Neume Search" plugin (if the manuscript's neumes are unpitched) or both the "Neume Search" and "Pitch Search" plugin (if the manuscript's neumes are pitched). Save the manuscript form. + +When you navigate to that manuscript's detail view, OMR search should be available in the search panel. + + ## Manuscript Inventory | Name | Provenance | Siglum | Cantus DB Record | IIIF Manifest | Supported | Served on cantus.simssa.ca | Notes | diff --git a/app/public/cantusdata/admin/admin.py b/app/public/cantusdata/admin/admin.py index efdcbb32..0d5d081c 100644 --- a/app/public/cantusdata/admin/admin.py +++ b/app/public/cantusdata/admin/admin.py @@ -43,7 +43,15 @@ class ManuscriptAdmin(admin.ModelAdmin): ), ( "Status", - {"fields": ["public", "chants_loaded", "is_mapped", "dbl_folio_img"]}, + { + "fields": [ + "public", + "chants_loaded", + "is_mapped", + "dbl_folio_img", + "plugins", + ] + }, ), ] readonly_fields = ( diff --git a/app/public/cantusdata/test/core/views/test_search_notation_view.py b/app/public/cantusdata/test/core/views/test_search_notation_view.py index 653640f6..1b17e66e 100644 --- a/app/public/cantusdata/test/core/views/test_search_notation_view.py +++ b/app/public/cantusdata/test/core/views/test_search_notation_view.py @@ -129,12 +129,15 @@ def test_get(self) -> None: } response_no_manuscript = self.client.get(url, params_no_manuscript) self.assertEqual(response_no_manuscript.status_code, 400) - params_no_type: dict[str, str | int] = {"q": "u d u", "manuscript": 123723} + params_no_type: dict[str, str | int] = { + "q": "u d u", + "manuscript_id": 123723, + } response_no_type = self.client.get(url, params_no_type) self.assertEqual(response_no_type.status_code, 400) params_no_q: dict[str, str | int] = { "type": "contour", - "manuscript": 123723, + "manuscript_id": 123723, } response_no_q = self.client.get(url, params_no_q) self.assertEqual(response_no_q.status_code, 400) @@ -142,7 +145,7 @@ def test_get(self) -> None: params: dict[str, str | int] = { "q": "u d u", "type": "contour", - "manuscript": 123723, + "manuscript_id": 123723, } response = self.client.get(url, params) self.assertEqual(response.status_code, 200) diff --git a/app/public/cantusdata/views/search_notation.py b/app/public/cantusdata/views/search_notation.py index b4bf96b3..e93d1923 100644 --- a/app/public/cantusdata/views/search_notation.py +++ b/app/public/cantusdata/views/search_notation.py @@ -35,7 +35,7 @@ class SolrQueryResultItem(TypedDict): class NotationSearchResultItem(TypedDict): boxes: list[dict[str, Union[int, str]]] contour: list[str] - semitones: list[str] + semitones: list[int] pnames: list[str] neumes: NotRequired[list[str]] @@ -53,7 +53,7 @@ class SearchNotationView(APIView): def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: q = request.GET.get("q", None) stype = request.GET.get("type", None) - manuscript_param = request.GET.get("manuscript", None) + manuscript_param = request.GET.get("manuscript_id", None) rows_param = request.GET.get("rows", "100") start_param = request.GET.get("start", "0") @@ -134,7 +134,9 @@ def do_query( result: NotationSearchResultItem = { "boxes": boxes, "contour": d["contour"].split("_"), - "semitones": d["semitone_intervals"].split("_"), + "semitones": [ + int(st) for st in d["semitone_intervals"].split("_") if len(st) > 0 + ], "pnames": d["pitch_names"].split("_"), } neume_names: Optional[str] = d.get("neume_names") diff --git a/nginx/public/node/frontend/public/js/app/collections/SearchNotationResultCollection.js b/nginx/public/node/frontend/public/js/app/collections/SearchNotationResultCollection.js index 4d46eba8..6efd0a4a 100644 --- a/nginx/public/node/frontend/public/js/app/collections/SearchNotationResultCollection.js +++ b/nginx/public/node/frontend/public/js/app/collections/SearchNotationResultCollection.js @@ -34,7 +34,7 @@ export default Backbone.Collection.extend({ var manuscript = _.result(this.parameters, 'manuscript'); if (manuscript) - queryParams.manuscript = manuscript; + queryParams.manuscript_id = manuscript; return GlobalVars.siteUrl + "notation-search/?" + Qs.stringify(queryParams); }, @@ -48,6 +48,7 @@ export default Backbone.Collection.extend({ */ parse: function (response) { + this.numFound = response && response.numFound || 0; return response && response.results || []; } }); diff --git a/nginx/public/node/frontend/public/js/app/search/omr-search/OMRSearchProvider.js b/nginx/public/node/frontend/public/js/app/search/omr-search/OMRSearchProvider.js index 3c08805d..bb5cbd1c 100644 --- a/nginx/public/node/frontend/public/js/app/search/omr-search/OMRSearchProvider.js +++ b/nginx/public/node/frontend/public/js/app/search/omr-search/OMRSearchProvider.js @@ -35,7 +35,7 @@ export default Marionette.Object.extend({ fields: [ { name: 'Neume', - type: 'neumes' + type: 'neume_names' } ] }, @@ -44,20 +44,21 @@ export default Marionette.Object.extend({ fields: [ { name: 'Pitch', - type: 'pnames' + type: 'pitch_names' }, { name: 'Pitch (invariant)', - type: 'pnames-invariant' + type: 'pitch_names_invariant' }, { name: 'Contour', type: 'contour' }, - { - name: 'Interval', - type: 'intervals' - } + // TODO: Implement interval search (see #875) + // { + // name: 'Interval', + // type: 'intervals' + // } ] } ], @@ -68,7 +69,7 @@ export default Marionette.Object.extend({ var manuscriptModel = options.manuscript; - this.manuscript = manuscriptModel.get('siglum_slug'); + this.manuscript = manuscriptModel.get('id'); this.neumeExemplars = new Backbone.Collection(manuscriptModel.get('neume_exemplars')); this.fields = []; @@ -146,9 +147,12 @@ export default Marionette.Object.extend({ getSearchMetadata: function () { + var numFound = this.results.numFound || 0; return { fieldName: this.field.name, - query: this.query + query: this.query, + displayedQuery: this.query, + numFound: numFound }; }, @@ -180,7 +184,7 @@ export default Marionette.Object.extend({ var contourChoices = new ContourChoiceView(); inputView.listenTo(contourChoices, 'use:contour', function(newQuery) { - inputView.insertSearchString(newQuery, false); + inputView.insertSearchString(newQuery, true); }); regions.searchHelper.show(contourChoices); }